Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

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:54 - Dateigröße: 49.48 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\tools;
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 implements tools_interface
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      * Get the column types for every database we support
0040      *
0041      * @return array
0042      */
0043      static public function get_dbms_type_map()
0044      {
0045          return array(
0046              'mysql_41'    => array(
0047                  'INT:'        => 'int(%d)',
0048                  'BINT'        => 'bigint(20)',
0049                  'ULINT'        => 'INT(10) UNSIGNED',
0050                  'UINT'        => 'mediumint(8) UNSIGNED',
0051                  'UINT:'        => 'int(%d) UNSIGNED',
0052                  'TINT:'        => 'tinyint(%d)',
0053                  'USINT'        => 'smallint(4) UNSIGNED',
0054                  'BOOL'        => 'tinyint(1) UNSIGNED',
0055                  'VCHAR'        => 'varchar(255)',
0056                  'VCHAR:'    => 'varchar(%d)',
0057                  'CHAR:'        => 'char(%d)',
0058                  'XSTEXT'    => 'text',
0059                  'XSTEXT_UNI'=> 'varchar(100)',
0060                  'STEXT'        => 'text',
0061                  'STEXT_UNI'    => 'varchar(255)',
0062                  'TEXT'        => 'text',
0063                  'TEXT_UNI'    => 'text',
0064                  'MTEXT'        => 'mediumtext',
0065                  'MTEXT_UNI'    => 'mediumtext',
0066                  'TIMESTAMP'    => 'int(11) UNSIGNED',
0067                  'DECIMAL'    => 'decimal(5,2)',
0068                  'DECIMAL:'    => 'decimal(%d,2)',
0069                  'PDECIMAL'    => 'decimal(6,3)',
0070                  'PDECIMAL:'    => 'decimal(%d,3)',
0071                  'VCHAR_UNI'    => 'varchar(255)',
0072                  'VCHAR_UNI:'=> 'varchar(%d)',
0073                  'VCHAR_CI'    => 'varchar(255)',
0074                  'VARBINARY'    => 'varbinary(255)',
0075              ),
0076   
0077              'mysql_40'    => array(
0078                  'INT:'        => 'int(%d)',
0079                  'BINT'        => 'bigint(20)',
0080                  'ULINT'        => 'INT(10) UNSIGNED',
0081                  'UINT'        => 'mediumint(8) UNSIGNED',
0082                  'UINT:'        => 'int(%d) UNSIGNED',
0083                  'TINT:'        => 'tinyint(%d)',
0084                  'USINT'        => 'smallint(4) UNSIGNED',
0085                  'BOOL'        => 'tinyint(1) UNSIGNED',
0086                  'VCHAR'        => 'varbinary(255)',
0087                  'VCHAR:'    => 'varbinary(%d)',
0088                  'CHAR:'        => 'binary(%d)',
0089                  'XSTEXT'    => 'blob',
0090                  'XSTEXT_UNI'=> 'blob',
0091                  'STEXT'        => 'blob',
0092                  'STEXT_UNI'    => 'blob',
0093                  'TEXT'        => 'blob',
0094                  'TEXT_UNI'    => 'blob',
0095                  'MTEXT'        => 'mediumblob',
0096                  'MTEXT_UNI'    => 'mediumblob',
0097                  'TIMESTAMP'    => 'int(11) UNSIGNED',
0098                  'DECIMAL'    => 'decimal(5,2)',
0099                  'DECIMAL:'    => 'decimal(%d,2)',
0100                  'PDECIMAL'    => 'decimal(6,3)',
0101                  'PDECIMAL:'    => 'decimal(%d,3)',
0102                  'VCHAR_UNI'    => 'blob',
0103                  'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')),
0104                  'VCHAR_CI'    => 'blob',
0105                  'VARBINARY'    => 'varbinary(255)',
0106              ),
0107   
0108              'oracle'    => array(
0109                  'INT:'        => 'number(%d)',
0110                  'BINT'        => 'number(20)',
0111                  'ULINT'        => 'number(10)',
0112                  'UINT'        => 'number(8)',
0113                  'UINT:'        => 'number(%d)',
0114                  'TINT:'        => 'number(%d)',
0115                  'USINT'        => 'number(4)',
0116                  'BOOL'        => 'number(1)',
0117                  'VCHAR'        => 'varchar2(255)',
0118                  'VCHAR:'    => 'varchar2(%d)',
0119                  'CHAR:'        => 'char(%d)',
0120                  'XSTEXT'    => 'varchar2(1000)',
0121                  'STEXT'        => 'varchar2(3000)',
0122                  'TEXT'        => 'clob',
0123                  'MTEXT'        => 'clob',
0124                  'XSTEXT_UNI'=> 'varchar2(300)',
0125                  'STEXT_UNI'    => 'varchar2(765)',
0126                  'TEXT_UNI'    => 'clob',
0127                  'MTEXT_UNI'    => 'clob',
0128                  'TIMESTAMP'    => 'number(11)',
0129                  'DECIMAL'    => 'number(5, 2)',
0130                  'DECIMAL:'    => 'number(%d, 2)',
0131                  'PDECIMAL'    => 'number(6, 3)',
0132                  'PDECIMAL:'    => 'number(%d, 3)',
0133                  'VCHAR_UNI'    => 'varchar2(765)',
0134                  'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')),
0135                  'VCHAR_CI'    => 'varchar2(255)',
0136                  'VARBINARY'    => 'raw(255)',
0137              ),
0138   
0139              'sqlite3'    => array(
0140                  'INT:'        => 'INT(%d)',
0141                  'BINT'        => 'BIGINT(20)',
0142                  'ULINT'        => 'INTEGER UNSIGNED',
0143                  'UINT'        => 'INTEGER UNSIGNED',
0144                  'UINT:'        => 'INTEGER UNSIGNED',
0145                  'TINT:'        => 'TINYINT(%d)',
0146                  'USINT'        => 'INTEGER UNSIGNED',
0147                  'BOOL'        => 'INTEGER UNSIGNED',
0148                  'VCHAR'        => 'VARCHAR(255)',
0149                  'VCHAR:'    => 'VARCHAR(%d)',
0150                  'CHAR:'        => 'CHAR(%d)',
0151                  'XSTEXT'    => 'TEXT(65535)',
0152                  'STEXT'        => 'TEXT(65535)',
0153                  'TEXT'        => 'TEXT(65535)',
0154                  'MTEXT'        => 'MEDIUMTEXT(16777215)',
0155                  'XSTEXT_UNI'=> 'TEXT(65535)',
0156                  'STEXT_UNI'    => 'TEXT(65535)',
0157                  'TEXT_UNI'    => 'TEXT(65535)',
0158                  'MTEXT_UNI'    => 'MEDIUMTEXT(16777215)',
0159                  'TIMESTAMP'    => 'INTEGER UNSIGNED', //'int(11) UNSIGNED',
0160                  'DECIMAL'    => 'DECIMAL(5,2)',
0161                  'DECIMAL:'    => 'DECIMAL(%d,2)',
0162                  'PDECIMAL'    => 'DECIMAL(6,3)',
0163                  'PDECIMAL:'    => 'DECIMAL(%d,3)',
0164                  'VCHAR_UNI'    => 'VARCHAR(255)',
0165                  'VCHAR_UNI:'=> 'VARCHAR(%d)',
0166                  'VCHAR_CI'    => 'VARCHAR(255)',
0167                  'VARBINARY'    => 'BLOB',
0168              ),
0169          );
0170      }
0171   
0172      /**
0173      * A list of types being unsigned for better reference in some db's
0174      * @var array
0175      */
0176      var $unsigned_types = array('ULINT', 'UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP');
0177   
0178      /**
0179      * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array).
0180      * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command.
0181      */
0182      var $return_statements = false;
0183   
0184      /**
0185      * Constructor. Set DB Object and set {@link $return_statements return_statements}.
0186      *
0187      * @param \phpbb\db\driver\driver_interface    $db                    Database connection
0188      * @param bool        $return_statements    True if only statements should be returned and no SQL being executed
0189      */
0190      public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false)
0191      {
0192          $this->db = $db;
0193          $this->return_statements = $return_statements;
0194   
0195          $this->dbms_type_map = self::get_dbms_type_map();
0196   
0197          // Determine mapping database type
0198          switch ($this->db->get_sql_layer())
0199          {
0200              case 'mysql':
0201                  $this->sql_layer = 'mysql_40';
0202              break;
0203   
0204              case 'mysql4':
0205                  if (version_compare($this->db->sql_server_info(true), '4.1.3', '>='))
0206                  {
0207                      $this->sql_layer = 'mysql_41';
0208                  }
0209                  else
0210                  {
0211                      $this->sql_layer = 'mysql_40';
0212                  }
0213              break;
0214   
0215              case 'mysqli':
0216                  $this->sql_layer = 'mysql_41';
0217              break;
0218   
0219              default:
0220                  $this->sql_layer = $this->db->get_sql_layer();
0221              break;
0222          }
0223      }
0224   
0225      /**
0226      * Setter for {@link $return_statements return_statements}.
0227      *
0228      * @param bool $return_statements True if SQL should not be executed but returned as strings
0229      * @return null
0230      */
0231      public function set_return_statements($return_statements)
0232      {
0233          $this->return_statements = $return_statements;
0234      }
0235   
0236      /**
0237       * {@inheritDoc}
0238       */
0239      function sql_list_tables()
0240      {
0241          switch ($this->db->get_sql_layer())
0242          {
0243              case 'mysql':
0244              case 'mysql4':
0245              case 'mysqli':
0246                  $sql = 'SHOW TABLES';
0247              break;
0248   
0249              case 'sqlite3':
0250                  $sql = 'SELECT name
0251                      FROM sqlite_master
0252                      WHERE type = "table"
0253                          AND name <> "sqlite_sequence"';
0254              break;
0255   
0256              case 'oracle':
0257                  $sql = 'SELECT table_name
0258                      FROM USER_TABLES';
0259              break;
0260          }
0261   
0262          $result = $this->db->sql_query($sql);
0263   
0264          $tables = array();
0265          while ($row = $this->db->sql_fetchrow($result))
0266          {
0267              $name = current($row);
0268              $tables[$name] = $name;
0269          }
0270          $this->db->sql_freeresult($result);
0271   
0272          return $tables;
0273      }
0274   
0275      /**
0276       * {@inheritDoc}
0277       */
0278      function sql_table_exists($table_name)
0279      {
0280          $this->db->sql_return_on_error(true);
0281          $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1);
0282          $this->db->sql_return_on_error(false);
0283   
0284          if ($result)
0285          {
0286              $this->db->sql_freeresult($result);
0287              return true;
0288          }
0289   
0290          return false;
0291      }
0292   
0293      /**
0294       * {@inheritDoc}
0295       */
0296      function sql_create_table($table_name, $table_data)
0297      {
0298          // holds the DDL for a column
0299          $columns = $statements = array();
0300   
0301          if ($this->sql_table_exists($table_name))
0302          {
0303              return $this->_sql_run_sql($statements);
0304          }
0305   
0306          // Begin transaction
0307          $statements[] = 'begin';
0308   
0309          // Determine if we have created a PRIMARY KEY in the earliest
0310          $primary_key_gen = false;
0311   
0312          // Determine if the table requires a sequence
0313          $create_sequence = false;
0314   
0315          // Begin table sql statement
0316          $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";
0317   
0318          // Iterate through the columns to create a table
0319          foreach ($table_data['COLUMNS'] as $column_name => $column_data)
0320          {
0321              // here lies an array, filled with information compiled on the column's data
0322              $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
0323   
0324              if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen"
0325              {
0326                  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);
0327              }
0328   
0329              // here we add the definition of the new column to the list of columns
0330              $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];
0331   
0332              // see if we have found a primary key set due to a column definition if we have found it, we can stop looking
0333              if (!$primary_key_gen)
0334              {
0335                  $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
0336              }
0337   
0338              // create sequence DDL based off of the existance of auto incrementing columns
0339              if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
0340              {
0341                  $create_sequence = $column_name;
0342              }
0343          }
0344   
0345          // this makes up all the columns in the create table statement
0346          $table_sql .= implode(",\n", $columns);
0347   
0348          // we have yet to create a primary key for this table,
0349          // this means that we can add the one we really wanted instead
0350          if (!$primary_key_gen)
0351          {
0352              // Write primary key
0353              if (isset($table_data['PRIMARY_KEY']))
0354              {
0355                  if (!is_array($table_data['PRIMARY_KEY']))
0356                  {
0357                      $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
0358                  }
0359   
0360                  switch ($this->sql_layer)
0361                  {
0362                      case 'mysql_40':
0363                      case 'mysql_41':
0364                      case 'sqlite3':
0365                          $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
0366                      break;
0367   
0368                      case 'oracle':
0369                          $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
0370                      break;
0371                  }
0372              }
0373          }
0374   
0375          // close the table
0376          switch ($this->sql_layer)
0377          {
0378              case 'mysql_41':
0379                  // make sure the table is in UTF-8 mode
0380                  $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;";
0381                  $statements[] = $table_sql;
0382              break;
0383   
0384              case 'mysql_40':
0385              case 'sqlite3':
0386                  $table_sql .= "\n);";
0387                  $statements[] = $table_sql;
0388              break;
0389   
0390              case 'oracle':
0391                  $table_sql .= "\n)";
0392                  $statements[] = $table_sql;
0393   
0394                  // do we need to add a sequence and a tigger for auto incrementing columns?
0395                  if ($create_sequence)
0396                  {
0397                      // create the actual sequence
0398                      $statements[] = "CREATE SEQUENCE {$table_name}_seq";
0399   
0400                      // the trigger is the mechanism by which we increment the counter
0401                      $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n";
0402                      $trigger .= "BEFORE INSERT ON {$table_name}\n";
0403                      $trigger .= "FOR EACH ROW WHEN (\n";
0404                      $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n";
0405                      $trigger .= ")\n";
0406                      $trigger .= "BEGIN\n";
0407                      $trigger .= "\tSELECT {$table_name}_seq.nextval\n";
0408                      $trigger .= "\tINTO :new.{$create_sequence}\n";
0409                      $trigger .= "\tFROM dual;\n";
0410                      $trigger .= "END;";
0411   
0412                      $statements[] = $trigger;
0413                  }
0414              break;
0415          }
0416   
0417          // Write Keys
0418          if (isset($table_data['KEYS']))
0419          {
0420              foreach ($table_data['KEYS'] as $key_name => $key_data)
0421              {
0422                  if (!is_array($key_data[1]))
0423                  {
0424                      $key_data[1] = array($key_data[1]);
0425                  }
0426   
0427                  $old_return_statements = $this->return_statements;
0428                  $this->return_statements = true;
0429   
0430                  $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]);
0431   
0432                  foreach ($key_stmts as $key_stmt)
0433                  {
0434                      $statements[] = $key_stmt;
0435                  }
0436   
0437                  $this->return_statements = $old_return_statements;
0438              }
0439          }
0440   
0441          // Commit Transaction
0442          $statements[] = 'commit';
0443   
0444          return $this->_sql_run_sql($statements);
0445      }
0446   
0447      /**
0448       * {@inheritDoc}
0449       */
0450      function perform_schema_changes($schema_changes)
0451      {
0452          if (empty($schema_changes))
0453          {
0454              return;
0455          }
0456   
0457          $statements = array();
0458          $sqlite = false;
0459   
0460          // For SQLite we need to perform the schema changes in a much more different way
0461          if ($this->db->get_sql_layer() == 'sqlite3' && $this->return_statements)
0462          {
0463              $sqlite_data = array();
0464              $sqlite = true;
0465          }
0466   
0467          // Drop tables?
0468          if (!empty($schema_changes['drop_tables']))
0469          {
0470              foreach ($schema_changes['drop_tables'] as $table)
0471              {
0472                  // only drop table if it exists
0473                  if ($this->sql_table_exists($table))
0474                  {
0475                      $result = $this->sql_table_drop($table);
0476                      if ($this->return_statements)
0477                      {
0478                          $statements = array_merge($statements, $result);
0479                      }
0480                  }
0481              }
0482          }
0483   
0484          // Add tables?
0485          if (!empty($schema_changes['add_tables']))
0486          {
0487              foreach ($schema_changes['add_tables'] as $table => $table_data)
0488              {
0489                  $result = $this->sql_create_table($table, $table_data);
0490                  if ($this->return_statements)
0491                  {
0492                      $statements = array_merge($statements, $result);
0493                  }
0494              }
0495          }
0496   
0497          // Change columns?
0498          if (!empty($schema_changes['change_columns']))
0499          {
0500              foreach ($schema_changes['change_columns'] as $table => $columns)
0501              {
0502                  foreach ($columns as $column_name => $column_data)
0503                  {
0504                      // If the column exists we change it, else we add it ;)
0505                      if ($column_exists = $this->sql_column_exists($table, $column_name))
0506                      {
0507                          $result = $this->sql_column_change($table, $column_name, $column_data, true);
0508                      }
0509                      else
0510                      {
0511                          $result = $this->sql_column_add($table, $column_name, $column_data, true);
0512                      }
0513   
0514                      if ($sqlite)
0515                      {
0516                          if ($column_exists)
0517                          {
0518                              $sqlite_data[$table]['change_columns'][] = $result;
0519                          }
0520                          else
0521                          {
0522                              $sqlite_data[$table]['add_columns'][] = $result;
0523                          }
0524                      }
0525                      else if ($this->return_statements)
0526                      {
0527                          $statements = array_merge($statements, $result);
0528                      }
0529                  }
0530              }
0531          }
0532   
0533          // Add columns?
0534          if (!empty($schema_changes['add_columns']))
0535          {
0536              foreach ($schema_changes['add_columns'] as $table => $columns)
0537              {
0538                  foreach ($columns as $column_name => $column_data)
0539                  {
0540                      // Only add the column if it does not exist yet
0541                      if ($column_exists = $this->sql_column_exists($table, $column_name))
0542                      {
0543                          continue;
0544                          // This is commented out here because it can take tremendous time on updates
0545  //                        $result = $this->sql_column_change($table, $column_name, $column_data, true);
0546                      }
0547                      else
0548                      {
0549                          $result = $this->sql_column_add($table, $column_name, $column_data, true);
0550                      }
0551   
0552                      if ($sqlite)
0553                      {
0554                          if ($column_exists)
0555                          {
0556                              continue;
0557  //                            $sqlite_data[$table]['change_columns'][] = $result;
0558                          }
0559                          else
0560                          {
0561                              $sqlite_data[$table]['add_columns'][] = $result;
0562                          }
0563                      }
0564                      else if ($this->return_statements)
0565                      {
0566                          $statements = array_merge($statements, $result);
0567                      }
0568                  }
0569              }
0570          }
0571   
0572          // Remove keys?
0573          if (!empty($schema_changes['drop_keys']))
0574          {
0575              foreach ($schema_changes['drop_keys'] as $table => $indexes)
0576              {
0577                  foreach ($indexes as $index_name)
0578                  {
0579                      if (!$this->sql_index_exists($table, $index_name))
0580                      {
0581                          continue;
0582                      }
0583   
0584                      $result = $this->sql_index_drop($table, $index_name);
0585   
0586                      if ($this->return_statements)
0587                      {
0588                          $statements = array_merge($statements, $result);
0589                      }
0590                  }
0591              }
0592          }
0593   
0594          // Drop columns?
0595          if (!empty($schema_changes['drop_columns']))
0596          {
0597              foreach ($schema_changes['drop_columns'] as $table => $columns)
0598              {
0599                  foreach ($columns as $column)
0600                  {
0601                      // Only remove the column if it exists...
0602                      if ($this->sql_column_exists($table, $column))
0603                      {
0604                          $result = $this->sql_column_remove($table, $column, true);
0605   
0606                          if ($sqlite)
0607                          {
0608                              $sqlite_data[$table]['drop_columns'][] = $result;
0609                          }
0610                          else if ($this->return_statements)
0611                          {
0612                              $statements = array_merge($statements, $result);
0613                          }
0614                      }
0615                  }
0616              }
0617          }
0618   
0619          // Add primary keys?
0620          if (!empty($schema_changes['add_primary_keys']))
0621          {
0622              foreach ($schema_changes['add_primary_keys'] as $table => $columns)
0623              {
0624                  $result = $this->sql_create_primary_key($table, $columns, true);
0625   
0626                  if ($sqlite)
0627                  {
0628                      $sqlite_data[$table]['primary_key'] = $result;
0629                  }
0630                  else if ($this->return_statements)
0631                  {
0632                      $statements = array_merge($statements, $result);
0633                  }
0634              }
0635          }
0636   
0637          // Add unique indexes?
0638          if (!empty($schema_changes['add_unique_index']))
0639          {
0640              foreach ($schema_changes['add_unique_index'] as $table => $index_array)
0641              {
0642                  foreach ($index_array as $index_name => $column)
0643                  {
0644                      if ($this->sql_unique_index_exists($table, $index_name))
0645                      {
0646                          continue;
0647                      }
0648   
0649                      $result = $this->sql_create_unique_index($table, $index_name, $column);
0650   
0651                      if ($this->return_statements)
0652                      {
0653                          $statements = array_merge($statements, $result);
0654                      }
0655                  }
0656              }
0657          }
0658   
0659          // Add indexes?
0660          if (!empty($schema_changes['add_index']))
0661          {
0662              foreach ($schema_changes['add_index'] as $table => $index_array)
0663              {
0664                  foreach ($index_array as $index_name => $column)
0665                  {
0666                      if ($this->sql_index_exists($table, $index_name))
0667                      {
0668                          continue;
0669                      }
0670   
0671                      $result = $this->sql_create_index($table, $index_name, $column);
0672   
0673                      if ($this->return_statements)
0674                      {
0675                          $statements = array_merge($statements, $result);
0676                      }
0677                  }
0678              }
0679          }
0680   
0681          if ($sqlite)
0682          {
0683              foreach ($sqlite_data as $table_name => $sql_schema_changes)
0684              {
0685                  // Create temporary table with original data
0686                  $statements[] = 'begin';
0687   
0688                  $sql = "SELECT sql
0689                      FROM sqlite_master
0690                      WHERE type = 'table'
0691                          AND name = '{$table_name}'
0692                      ORDER BY type DESC, name;";
0693                  $result = $this->db->sql_query($sql);
0694   
0695                  if (!$result)
0696                  {
0697                      continue;
0698                  }
0699   
0700                  $row = $this->db->sql_fetchrow($result);
0701                  $this->db->sql_freeresult($result);
0702   
0703                  // Create a backup table and populate it, destroy the existing one
0704                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
0705                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
0706                  $statements[] = 'DROP TABLE ' . $table_name;
0707   
0708                  // Get the columns...
0709                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
0710   
0711                  $plain_table_cols = trim($matches[1]);
0712                  $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols);
0713                  $column_list = array();
0714   
0715                  foreach ($new_table_cols as $declaration)
0716                  {
0717                      $entities = preg_split('#\s+#', trim($declaration));
0718                      if ($entities[0] == 'PRIMARY')
0719                      {
0720                          continue;
0721                      }
0722                      $column_list[] = $entities[0];
0723                  }
0724   
0725                  // note down the primary key notation because sqlite only supports adding it to the end for the new table
0726                  $primary_key = false;
0727                  $_new_cols = array();
0728   
0729                  foreach ($new_table_cols as $key => $declaration)
0730                  {
0731                      $entities = preg_split('#\s+#', trim($declaration));
0732                      if ($entities[0] == 'PRIMARY')
0733                      {
0734                          $primary_key = $declaration;
0735                          continue;
0736                      }
0737                      $_new_cols[] = $declaration;
0738                  }
0739   
0740                  $new_table_cols = $_new_cols;
0741   
0742                  // First of all... change columns
0743                  if (!empty($sql_schema_changes['change_columns']))
0744                  {
0745                      foreach ($sql_schema_changes['change_columns'] as $column_sql)
0746                      {
0747                          foreach ($new_table_cols as $key => $declaration)
0748                          {
0749                              $entities = preg_split('#\s+#', trim($declaration));
0750                              if (strpos($column_sql, $entities[0] . ' ') === 0)
0751                              {
0752                                  $new_table_cols[$key] = $column_sql;
0753                              }
0754                          }
0755                      }
0756                  }
0757   
0758                  if (!empty($sql_schema_changes['add_columns']))
0759                  {
0760                      foreach ($sql_schema_changes['add_columns'] as $column_sql)
0761                      {
0762                          $new_table_cols[] = $column_sql;
0763                      }
0764                  }
0765   
0766                  // Now drop them...
0767                  if (!empty($sql_schema_changes['drop_columns']))
0768                  {
0769                      foreach ($sql_schema_changes['drop_columns'] as $column_name)
0770                      {
0771                          // Remove from column list...
0772                          $new_column_list = array();
0773                          foreach ($column_list as $key => $value)
0774                          {
0775                              if ($value === $column_name)
0776                              {
0777                                  continue;
0778                              }
0779   
0780                              $new_column_list[] = $value;
0781                          }
0782   
0783                          $column_list = $new_column_list;
0784   
0785                          // Remove from table...
0786                          $_new_cols = array();
0787                          foreach ($new_table_cols as $key => $declaration)
0788                          {
0789                              $entities = preg_split('#\s+#', trim($declaration));
0790                              if (strpos($column_name . ' ', $entities[0] . ' ') === 0)
0791                              {
0792                                  continue;
0793                              }
0794                              $_new_cols[] = $declaration;
0795                          }
0796                          $new_table_cols = $_new_cols;
0797                      }
0798                  }
0799   
0800                  // Primary key...
0801                  if (!empty($sql_schema_changes['primary_key']))
0802                  {
0803                      $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')';
0804                  }
0805                  // Add a new one or the old primary key
0806                  else if ($primary_key !== false)
0807                  {
0808                      $new_table_cols[] = $primary_key;
0809                  }
0810   
0811                  $columns = implode(',', $column_list);
0812   
0813                  // create a new table and fill it up. destroy the temp one
0814                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');';
0815                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
0816                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
0817   
0818                  $statements[] = 'commit';
0819              }
0820          }
0821   
0822          if ($this->return_statements)
0823          {
0824              return $statements;
0825          }
0826      }
0827   
0828      /**
0829       * {@inheritDoc}
0830       */
0831      function sql_list_columns($table_name)
0832      {
0833          $columns = array();
0834   
0835          switch ($this->sql_layer)
0836          {
0837              case 'mysql_40':
0838              case 'mysql_41':
0839                  $sql = "SHOW COLUMNS FROM $table_name";
0840              break;
0841   
0842              case 'oracle':
0843                  $sql = "SELECT column_name
0844                      FROM user_tab_columns
0845                      WHERE LOWER(table_name) = '" . strtolower($table_name) . "'";
0846              break;
0847   
0848              case 'sqlite3':
0849                  $sql = "SELECT sql
0850                      FROM sqlite_master
0851                      WHERE type = 'table'
0852                          AND name = '{$table_name}'";
0853   
0854                  $result = $this->db->sql_query($sql);
0855   
0856                  if (!$result)
0857                  {
0858                      return false;
0859                  }
0860   
0861                  $row = $this->db->sql_fetchrow($result);
0862                  $this->db->sql_freeresult($result);
0863   
0864                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
0865   
0866                  $cols = trim($matches[1]);
0867                  $col_array = preg_split('/,(?![\s\w]+\))/m', $cols);
0868   
0869                  foreach ($col_array as $declaration)
0870                  {
0871                      $entities = preg_split('#\s+#', trim($declaration));
0872                      if ($entities[0] == 'PRIMARY')
0873                      {
0874                          continue;
0875                      }
0876   
0877                      $column = strtolower($entities[0]);
0878                      $columns[$column] = $column;
0879                  }
0880   
0881                  return $columns;
0882              break;
0883          }
0884   
0885          $result = $this->db->sql_query($sql);
0886   
0887          while ($row = $this->db->sql_fetchrow($result))
0888          {
0889              $column = strtolower(current($row));
0890              $columns[$column] = $column;
0891          }
0892          $this->db->sql_freeresult($result);
0893   
0894          return $columns;
0895      }
0896   
0897      /**
0898       * {@inheritDoc}
0899       */
0900      function sql_column_exists($table_name, $column_name)
0901      {
0902          $columns = $this->sql_list_columns($table_name);
0903   
0904          return isset($columns[$column_name]);
0905      }
0906   
0907      /**
0908       * {@inheritDoc}
0909       */
0910      function sql_index_exists($table_name, $index_name)
0911      {
0912          switch ($this->sql_layer)
0913          {
0914              case 'mysql_40':
0915              case 'mysql_41':
0916                  $sql = 'SHOW KEYS
0917                      FROM ' . $table_name;
0918                  $col = 'Key_name';
0919              break;
0920   
0921              case 'oracle':
0922                  $sql = "SELECT index_name
0923                      FROM user_indexes
0924                      WHERE table_name = '" . strtoupper($table_name) . "'
0925                          AND generated = 'N'
0926                          AND uniqueness = 'NONUNIQUE'";
0927                  $col = 'index_name';
0928              break;
0929   
0930              case 'sqlite3':
0931                  $sql = "PRAGMA index_list('" . $table_name . "');";
0932                  $col = 'name';
0933              break;
0934          }
0935   
0936          $result = $this->db->sql_query($sql);
0937          while ($row = $this->db->sql_fetchrow($result))
0938          {
0939              if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
0940              {
0941                  continue;
0942              }
0943   
0944              // These DBMS prefix index name with the table name
0945              switch ($this->sql_layer)
0946              {
0947                  case 'oracle':
0948                  case 'sqlite3':
0949                      $row[$col] = substr($row[$col], strlen($table_name) + 1);
0950                  break;
0951              }
0952   
0953              if (strtolower($row[$col]) == strtolower($index_name))
0954              {
0955                  $this->db->sql_freeresult($result);
0956                  return true;
0957              }
0958          }
0959          $this->db->sql_freeresult($result);
0960   
0961          return false;
0962      }
0963   
0964      /**
0965       * {@inheritDoc}
0966       */
0967      function sql_unique_index_exists($table_name, $index_name)
0968      {
0969          switch ($this->sql_layer)
0970          {
0971              case 'mysql_40':
0972              case 'mysql_41':
0973                  $sql = 'SHOW KEYS
0974                      FROM ' . $table_name;
0975                  $col = 'Key_name';
0976              break;
0977   
0978              case 'oracle':
0979                  $sql = "SELECT index_name, table_owner
0980                      FROM user_indexes
0981                      WHERE table_name = '" . strtoupper($table_name) . "'
0982                          AND generated = 'N'
0983                          AND uniqueness = 'UNIQUE'";
0984                  $col = 'index_name';
0985              break;
0986   
0987              case 'sqlite3':
0988                  $sql = "PRAGMA index_list('" . $table_name . "');";
0989                  $col = 'name';
0990              break;
0991          }
0992   
0993          $result = $this->db->sql_query($sql);
0994          while ($row = $this->db->sql_fetchrow($result))
0995          {
0996              if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY'))
0997              {
0998                  continue;
0999              }
1000   
1001              if ($this->sql_layer == 'sqlite3' && !$row['unique'])
1002              {
1003                  continue;
1004              }
1005   
1006              // These DBMS prefix index name with the table name
1007              switch ($this->sql_layer)
1008              {
1009                  case 'oracle':
1010                      // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name
1011                      if (strpos($row[$col], 'U_') === 0)
1012                      {
1013                          $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1);
1014                      }
1015                      else if (strpos($row[$col], strtoupper($table_name)) === 0)
1016                      {
1017                          $row[$col] = substr($row[$col], strlen($table_name) + 1);
1018                      }
1019                  break;
1020   
1021                  case 'sqlite3':
1022                      $row[$col] = substr($row[$col], strlen($table_name) + 1);
1023                  break;
1024              }
1025   
1026              if (strtolower($row[$col]) == strtolower($index_name))
1027              {
1028                  $this->db->sql_freeresult($result);
1029                  return true;
1030              }
1031          }
1032          $this->db->sql_freeresult($result);
1033   
1034          return false;
1035      }
1036   
1037      /**
1038      * Private method for performing sql statements (either execute them or return them)
1039      * @access private
1040      */
1041      function _sql_run_sql($statements)
1042      {
1043          if ($this->return_statements)
1044          {
1045              return $statements;
1046          }
1047   
1048          // We could add error handling here...
1049          foreach ($statements as $sql)
1050          {
1051              if ($sql === 'begin')
1052              {
1053                  $this->db->sql_transaction('begin');
1054              }
1055              else if ($sql === 'commit')
1056              {
1057                  $this->db->sql_transaction('commit');
1058              }
1059              else
1060              {
1061                  $this->db->sql_query($sql);
1062              }
1063          }
1064   
1065          return true;
1066      }
1067   
1068      /**
1069      * Function to prepare some column information for better usage
1070      * @access private
1071      */
1072      function sql_prepare_column_data($table_name, $column_name, $column_data)
1073      {
1074          if (strlen($column_name) > 30)
1075          {
1076              trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
1077          }
1078   
1079          // Get type
1080          list($column_type) = $this->get_column_type($column_data[0]);
1081   
1082          // Adjust default value if db-dependent specified
1083          if (is_array($column_data[1]))
1084          {
1085              $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
1086          }
1087   
1088          $sql = '';
1089   
1090          $return_array = array();
1091   
1092          switch ($this->sql_layer)
1093          {
1094              case 'mysql_40':
1095              case 'mysql_41':
1096                  $sql .= " {$column_type} ";
1097   
1098                  // For hexadecimal values do not use single quotes
1099                  if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob')
1100                  {
1101                      $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}";
1102                  }
1103   
1104                  if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment'))
1105                  {
1106                      $sql .= 'NOT NULL';
1107                  }
1108                  else
1109                  {
1110                      $sql .= 'NULL';
1111                  }
1112   
1113                  if (isset($column_data[2]))
1114                  {
1115                      if ($column_data[2] == 'auto_increment')
1116                      {
1117                          $sql .= ' auto_increment';
1118                      }
1119                      else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort')
1120                      {
1121                          $sql .= ' COLLATE utf8_unicode_ci';
1122                      }
1123                  }
1124   
1125                  if (isset($column_data['after']))
1126                  {
1127                      $return_array['after'] = $column_data['after'];
1128                  }
1129   
1130              break;
1131   
1132              case 'oracle':
1133                  $sql .= " {$column_type} ";
1134                  $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}" : '';
1135   
1136                  // In Oracle empty strings ('') are treated as NULL.
1137                  // Therefore in oracle we allow NULL's for all DEFAULT '' entries
1138                  // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields)
1139                  if (!preg_match('/number/i', $column_type))
1140                  {
1141                      $sql .= ($column_data[1] === '' || $column_data[1] === null) ? '' : 'NOT NULL';
1142                  }
1143   
1144                  $return_array['auto_increment'] = false;
1145                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
1146                  {
1147                      $return_array['auto_increment'] = true;
1148                  }
1149   
1150              break;
1151   
1152              case 'sqlite3':
1153                  $return_array['primary_key_set'] = false;
1154                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
1155                  {
1156                      $sql .= ' INTEGER PRIMARY KEY AUTOINCREMENT';
1157                      $return_array['primary_key_set'] = true;
1158                  }
1159                  else
1160                  {
1161                      $sql .= ' ' . $column_type;
1162                  }
1163   
1164                  if (!is_null($column_data[1]))
1165                  {
1166                      $sql .= ' NOT NULL ';
1167                      $sql .= "DEFAULT '{$column_data[1]}'";
1168                  }
1169   
1170              break;
1171          }
1172   
1173          $return_array['column_type_sql'] = $sql;
1174   
1175          return $return_array;
1176      }
1177   
1178      /**
1179      * Get the column's database type from the type map
1180      *
1181      * @param string $column_map_type
1182      * @return array        column type for this database
1183      *                    and map type without length
1184      */
1185      function get_column_type($column_map_type)
1186      {
1187          $column_type = '';
1188          if (strpos($column_map_type, ':') !== false)
1189          {
1190              list($orig_column_type, $column_length) = explode(':', $column_map_type);
1191              if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']))
1192              {
1193                  $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length);
1194              }
1195              else
1196              {
1197                  if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule']))
1198                  {
1199                      switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0])
1200                      {
1201                          case 'div':
1202                              $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1];
1203                              $column_length = ceil($column_length);
1204                              $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
1205                          break;
1206                      }
1207                  }
1208   
1209                  if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit']))
1210                  {
1211                      switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0])
1212                      {
1213                          case 'mult':
1214                              $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1];
1215                              if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2])
1216                              {
1217                                  $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3];
1218                              }
1219                              else
1220                              {
1221                                  $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
1222                              }
1223                          break;
1224                      }
1225                  }
1226              }
1227              $orig_column_type .= ':';
1228          }
1229          else
1230          {
1231              $orig_column_type = $column_map_type;
1232              $column_type = $this->dbms_type_map[$this->sql_layer][$column_map_type];
1233          }
1234   
1235          return array($column_type, $orig_column_type);
1236      }
1237   
1238      /**
1239       * {@inheritDoc}
1240       */
1241      function sql_column_add($table_name, $column_name, $column_data, $inline = false)
1242      {
1243          $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
1244          $statements = array();
1245   
1246          switch ($this->sql_layer)
1247          {
1248              case 'mysql_40':
1249              case 'mysql_41':
1250                  $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : '';
1251                  $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after;
1252              break;
1253   
1254              case 'oracle':
1255                  // Does not support AFTER, only through temporary table
1256                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];
1257              break;
1258   
1259              case 'sqlite3':
1260                  if ($inline && $this->return_statements)
1261                  {
1262                      return $column_name . ' ' . $column_data['column_type_sql'];
1263                  }
1264   
1265                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];
1266              break;
1267          }
1268   
1269          return $this->_sql_run_sql($statements);
1270      }
1271   
1272      /**
1273       * {@inheritDoc}
1274       */
1275      function sql_column_remove($table_name, $column_name, $inline = false)
1276      {
1277          $statements = array();
1278   
1279          switch ($this->sql_layer)
1280          {
1281              case 'mysql_40':
1282              case 'mysql_41':
1283                  $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`';
1284              break;
1285   
1286              case 'oracle':
1287                  $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;
1288              break;
1289   
1290              case 'sqlite3':
1291   
1292                  if ($inline && $this->return_statements)
1293                  {
1294                      return $column_name;
1295                  }
1296   
1297                  $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name, $column_name);
1298                  if (empty($recreate_queries))
1299                  {
1300                      break;
1301                  }
1302   
1303                  $statements[] = 'begin';
1304   
1305                  $sql_create_table = array_shift($recreate_queries);
1306   
1307                  // Create a backup table and populate it, destroy the existing one
1308                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table);
1309                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
1310                  $statements[] = 'DROP TABLE ' . $table_name;
1311   
1312                  preg_match('#\((.*)\)#s', $sql_create_table, $matches);
1313   
1314                  $new_table_cols = trim($matches[1]);
1315                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
1316                  $column_list = array();
1317   
1318                  foreach ($old_table_cols as $declaration)
1319                  {
1320                      $entities = preg_split('#\s+#', trim($declaration));
1321                      if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name)
1322                      {
1323                          continue;
1324                      }
1325                      $column_list[] = $entities[0];
1326                  }
1327   
1328                  $columns = implode(',', $column_list);
1329   
1330                  $new_table_cols = trim(preg_replace('/' . $column_name . '\b[^,]+(?:,|$)/m', '', $new_table_cols));
1331                  if (substr($new_table_cols, -1) === ',')
1332                  {
1333                      // Remove the comma from the last entry again
1334                      $new_table_cols = substr($new_table_cols, 0, -1);
1335                  }
1336   
1337                  // create a new table and fill it up. destroy the temp one
1338                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
1339                  $statements = array_merge($statements, $recreate_queries);
1340   
1341                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
1342                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
1343   
1344                  $statements[] = 'commit';
1345              break;
1346          }
1347   
1348          return $this->_sql_run_sql($statements);
1349      }
1350   
1351      /**
1352       * {@inheritDoc}
1353       */
1354      function sql_index_drop($table_name, $index_name)
1355      {
1356          $statements = array();
1357   
1358          switch ($this->sql_layer)
1359          {
1360              case 'mysql_40':
1361              case 'mysql_41':
1362                  $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name;
1363              break;
1364   
1365              case 'oracle':
1366              case 'sqlite3':
1367                  $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name;
1368              break;
1369          }
1370   
1371          return $this->_sql_run_sql($statements);
1372      }
1373   
1374      /**
1375       * {@inheritDoc}
1376       */
1377      function sql_table_drop($table_name)
1378      {
1379          $statements = array();
1380   
1381          if (!$this->sql_table_exists($table_name))
1382          {
1383              return $this->_sql_run_sql($statements);
1384          }
1385   
1386          // the most basic operation, get rid of the table
1387          $statements[] = 'DROP TABLE ' . $table_name;
1388   
1389          switch ($this->sql_layer)
1390          {
1391              case 'oracle':
1392                  $sql = 'SELECT A.REFERENCED_NAME
1393                      FROM USER_DEPENDENCIES A, USER_TRIGGERS B
1394                      WHERE A.REFERENCED_TYPE = \'SEQUENCE\'
1395                          AND A.NAME = B.TRIGGER_NAME
1396                          AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'";
1397                  $result = $this->db->sql_query($sql);
1398   
1399                  // any sequences ref'd to this table's triggers?
1400                  while ($row = $this->db->sql_fetchrow($result))
1401                  {
1402                      $statements[] = "DROP SEQUENCE {$row['referenced_name']}";
1403                  }
1404                  $this->db->sql_freeresult($result);
1405              break;
1406          }
1407   
1408          return $this->_sql_run_sql($statements);
1409      }
1410   
1411      /**
1412       * {@inheritDoc}
1413       */
1414      function sql_create_primary_key($table_name, $column, $inline = false)
1415      {
1416          $statements = array();
1417   
1418          switch ($this->sql_layer)
1419          {
1420              case 'mysql_40':
1421              case 'mysql_41':
1422                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')';
1423              break;
1424   
1425              case 'oracle':
1426                  $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')';
1427              break;
1428   
1429              case 'sqlite3':
1430   
1431                  if ($inline && $this->return_statements)
1432                  {
1433                      return $column;
1434                  }
1435   
1436                  $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name);
1437                  if (empty($recreate_queries))
1438                  {
1439                      break;
1440                  }
1441   
1442                  $statements[] = 'begin';
1443   
1444                  $sql_create_table = array_shift($recreate_queries);
1445   
1446                  // Create a backup table and populate it, destroy the existing one
1447                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table);
1448                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
1449                  $statements[] = 'DROP TABLE ' . $table_name;
1450   
1451                  preg_match('#\((.*)\)#s', $sql_create_table, $matches);
1452   
1453                  $new_table_cols = trim($matches[1]);
1454                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
1455                  $column_list = array();
1456   
1457                  foreach ($old_table_cols as $declaration)
1458                  {
1459                      $entities = preg_split('#\s+#', trim($declaration));
1460                      if ($entities[0] == 'PRIMARY')
1461                      {
1462                          continue;
1463                      }
1464                      $column_list[] = $entities[0];
1465                  }
1466   
1467                  $columns = implode(',', $column_list);
1468   
1469                  // create a new table and fill it up. destroy the temp one
1470                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));';
1471                  $statements = array_merge($statements, $recreate_queries);
1472   
1473                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
1474                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
1475   
1476                  $statements[] = 'commit';
1477              break;
1478          }
1479   
1480          return $this->_sql_run_sql($statements);
1481      }
1482   
1483      /**
1484       * {@inheritDoc}
1485       */
1486      function sql_create_unique_index($table_name, $index_name, $column)
1487      {
1488          $statements = array();
1489   
1490          $this->check_index_name_length($table_name, $index_name);
1491   
1492          switch ($this->sql_layer)
1493          {
1494              case 'oracle':
1495              case 'sqlite3':
1496                  $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
1497              break;
1498   
1499              case 'mysql_40':
1500              case 'mysql_41':
1501                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')';
1502              break;
1503          }
1504   
1505          return $this->_sql_run_sql($statements);
1506      }
1507   
1508      /**
1509       * {@inheritDoc}
1510       */
1511      function sql_create_index($table_name, $index_name, $column)
1512      {
1513          $statements = array();
1514   
1515          $this->check_index_name_length($table_name, $index_name);
1516   
1517          // remove index length unless MySQL4
1518          if ('mysql_40' != $this->sql_layer)
1519          {
1520              $column = preg_replace('#:.*$#', '', $column);
1521          }
1522   
1523          switch ($this->sql_layer)
1524          {
1525              case 'oracle':
1526              case 'sqlite3':
1527                  $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
1528              break;
1529   
1530              case 'mysql_40':
1531                  // add index size to definition as required by MySQL4
1532                  foreach ($column as $i => $col)
1533                  {
1534                      if (false !== strpos($col, ':'))
1535                      {
1536                          list($col, $index_size) = explode(':', $col);
1537                          $column[$i] = "$col($index_size)";
1538                      }
1539                  }
1540              // no break
1541              case 'mysql_41':
1542                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')';
1543              break;
1544          }
1545   
1546          return $this->_sql_run_sql($statements);
1547      }
1548   
1549      /**
1550       * Check whether the index name is too long
1551       *
1552       * @param string $table_name
1553       * @param string $index_name
1554       */
1555      protected function check_index_name_length($table_name, $index_name)
1556      {
1557          $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config)
1558          if (strlen($table_name . $index_name) - strlen($table_prefix) > 24)
1559          {
1560              $max_length = strlen($table_prefix) + 24;
1561              trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR);
1562          }
1563      }
1564   
1565      /**
1566       * {@inheritDoc}
1567       */
1568      function sql_list_index($table_name)
1569      {
1570          $index_array = array();
1571   
1572          switch ($this->sql_layer)
1573          {
1574              case 'mysql_40':
1575              case 'mysql_41':
1576                  $sql = 'SHOW KEYS
1577                      FROM ' . $table_name;
1578                  $col = 'Key_name';
1579                  break;
1580   
1581              case 'oracle':
1582                  $sql = "SELECT index_name
1583                      FROM user_indexes
1584                      WHERE table_name = '" . strtoupper($table_name) . "'
1585                          AND generated = 'N'
1586                          AND uniqueness = 'NONUNIQUE'";
1587                  $col = 'index_name';
1588                  break;
1589   
1590              case 'sqlite3':
1591                  $sql = "PRAGMA index_info('" . $table_name . "');";
1592                  $col = 'name';
1593                  break;
1594          }
1595   
1596          $result = $this->db->sql_query($sql);
1597          while ($row = $this->db->sql_fetchrow($result))
1598          {
1599              if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
1600              {
1601                  continue;
1602              }
1603   
1604              switch ($this->sql_layer)
1605              {
1606                  case 'oracle':
1607                  case 'sqlite3':
1608                      $row[$col] = substr($row[$col], strlen($table_name) + 1);
1609                      break;
1610              }
1611   
1612              $index_array[] = $row[$col];
1613          }
1614          $this->db->sql_freeresult($result);
1615   
1616          return array_map('strtolower', $index_array);
1617      }
1618   
1619      /**
1620       * Removes table_name from the index_name if it is at the beginning
1621       *
1622       * @param $table_name
1623       * @param $index_name
1624       * @return string
1625       */
1626      protected function strip_table_name_from_index_name($table_name, $index_name)
1627      {
1628          return (strpos(strtoupper($index_name), strtoupper($table_name)) === 0) ? substr($index_name, strlen($table_name) + 1) : $index_name;
1629      }
1630   
1631      /**
1632       * {@inheritDoc}
1633       */
1634      function sql_column_change($table_name, $column_name, $column_data, $inline = false)
1635      {
1636          $original_column_data = $column_data;
1637          $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
1638          $statements = array();
1639   
1640          switch ($this->sql_layer)
1641          {
1642              case 'mysql_40':
1643              case 'mysql_41':
1644                  $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql'];
1645              break;
1646   
1647              case 'oracle':
1648                  // We need the data here
1649                  $old_return_statements = $this->return_statements;
1650                  $this->return_statements = true;
1651   
1652                  // Get list of existing indexes
1653                  $indexes = $this->get_existing_indexes($table_name, $column_name);
1654                  $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true);
1655   
1656                  // Drop any indexes
1657                  if (!empty($indexes) || !empty($unique_indexes))
1658                  {
1659                      $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes));
1660                      foreach ($drop_indexes as $index_name)
1661                      {
1662                          $result = $this->sql_index_drop($table_name, $this->strip_table_name_from_index_name($table_name, $index_name));
1663                          $statements = array_merge($statements, $result);
1664                      }
1665                  }
1666   
1667                  $temp_column_name = 'temp_' . substr(md5($column_name), 0, 25);
1668                  // Add a temporary table with the new type
1669                  $result = $this->sql_column_add($table_name, $temp_column_name, $original_column_data);
1670                  $statements = array_merge($statements, $result);
1671   
1672                  // Copy the data to the new column
1673                  $statements[] = 'UPDATE ' . $table_name . ' SET ' . $temp_column_name . ' = ' . $column_name;
1674   
1675                  // Drop the original column
1676                  $result = $this->sql_column_remove($table_name, $column_name);
1677                  $statements = array_merge($statements, $result);
1678   
1679                  // Recreate the original column with the new type
1680                  $result = $this->sql_column_add($table_name, $column_name, $original_column_data);
1681                  $statements = array_merge($statements, $result);
1682   
1683                  if (!empty($indexes))
1684                  {
1685                      // Recreate indexes after we changed the column
1686                      foreach ($indexes as $index_name => $index_data)
1687                      {
1688                          $result = $this->sql_create_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data);
1689                          $statements = array_merge($statements, $result);
1690                      }
1691                  }
1692   
1693                  if (!empty($unique_indexes))
1694                  {
1695                      // Recreate unique indexes after we changed the column
1696                      foreach ($unique_indexes as $index_name => $index_data)
1697                      {
1698                          $result = $this->sql_create_unique_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data);
1699                          $statements = array_merge($statements, $result);
1700                      }
1701                  }
1702   
1703                  // Copy the data to the original column
1704                  $statements[] = 'UPDATE ' . $table_name . ' SET ' . $column_name . ' = ' . $temp_column_name;
1705   
1706                  // Drop the temporary column again
1707                  $result = $this->sql_column_remove($table_name, $temp_column_name);
1708                  $statements = array_merge($statements, $result);
1709   
1710                  $this->return_statements = $old_return_statements;
1711              break;
1712   
1713              case 'sqlite3':
1714   
1715                  if ($inline && $this->return_statements)
1716                  {
1717                      return $column_name . ' ' . $column_data['column_type_sql'];
1718                  }
1719   
1720                  $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name);
1721                  if (empty($recreate_queries))
1722                  {
1723                      break;
1724                  }
1725   
1726                  $statements[] = 'begin';
1727   
1728                  $sql_create_table = array_shift($recreate_queries);
1729   
1730                  // Create a temp table and populate it, destroy the existing one
1731                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table);
1732                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
1733                  $statements[] = 'DROP TABLE ' . $table_name;
1734   
1735                  preg_match('#\((.*)\)#s', $sql_create_table, $matches);
1736   
1737                  $new_table_cols = trim($matches[1]);
1738                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
1739                  $column_list = array();
1740   
1741                  foreach ($old_table_cols as $key => $declaration)
1742                  {
1743                      $declaration = trim($declaration);
1744   
1745                      // Check for the beginning of the constraint section and stop
1746                      if (preg_match('/[^\(]*\s*PRIMARY KEY\s+\(/', $declaration) ||
1747                          preg_match('/[^\(]*\s*UNIQUE\s+\(/', $declaration) ||
1748                          preg_match('/[^\(]*\s*FOREIGN KEY\s+\(/', $declaration) ||
1749                          preg_match('/[^\(]*\s*CHECK\s+\(/', $declaration))
1750                      {
1751                          break;
1752                      }
1753   
1754                      $entities = preg_split('#\s+#', $declaration);
1755                      $column_list[] = $entities[0];
1756                      if ($entities[0] == $column_name)
1757                      {
1758                          $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql'];
1759                      }
1760                  }
1761   
1762                  $columns = implode(',', $column_list);
1763   
1764                  // Create a new table and fill it up. destroy the temp one
1765                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');';
1766                  $statements = array_merge($statements, $recreate_queries);
1767   
1768                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
1769                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
1770   
1771                  $statements[] = 'commit';
1772   
1773              break;
1774          }
1775   
1776          return $this->_sql_run_sql($statements);
1777      }
1778   
1779      /**
1780      * Get a list with existing indexes for the column
1781      *
1782      * @param string $table_name
1783      * @param string $column_name
1784      * @param bool $unique Should we get unique indexes or normal ones
1785      * @return array        Array with Index name => columns
1786      */
1787      public function get_existing_indexes($table_name, $column_name, $unique = false)
1788      {
1789          switch ($this->sql_layer)
1790          {
1791              case 'mysql_40':
1792              case 'mysql_41':
1793              case 'sqlite3':
1794                  // Not supported
1795                  throw new \Exception('DBMS is not supported');
1796              break;
1797          }
1798   
1799          $sql = '';
1800          $existing_indexes = array();
1801   
1802          switch ($this->sql_layer)
1803          {
1804              case 'oracle':
1805                  $sql = "SELECT ix.index_name  AS phpbb_index_name, ix.uniqueness AS is_unique
1806                      FROM all_ind_columns ixc, all_indexes ix
1807                      WHERE ix.index_name = ixc.index_name
1808                          AND ixc.table_name = '" . strtoupper($table_name) . "'
1809                          AND ixc.column_name = '" . strtoupper($column_name) . "'";
1810              break;
1811          }
1812   
1813          $result = $this->db->sql_query($sql);
1814          while ($row = $this->db->sql_fetchrow($result))
1815          {
1816              if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE'))
1817              {
1818                  $existing_indexes[$row['phpbb_index_name']] = array();
1819              }
1820          }
1821          $this->db->sql_freeresult($result);
1822   
1823          if (empty($existing_indexes))
1824          {
1825              return array();
1826          }
1827   
1828          switch ($this->sql_layer)
1829          {
1830              case 'oracle':
1831                  $sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name
1832                      FROM all_ind_columns
1833                      WHERE table_name = '" . strtoupper($table_name) . "'
1834                          AND " . $this->db->sql_in_set('index_name', array_keys($existing_indexes));
1835              break;
1836          }
1837   
1838          $result = $this->db->sql_query($sql);
1839          while ($row = $this->db->sql_fetchrow($result))
1840          {
1841              $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name'];
1842          }
1843          $this->db->sql_freeresult($result);
1844   
1845          return $existing_indexes;
1846      }
1847   
1848      /**
1849      * Returns the Queries which are required to recreate a table including indexes
1850      *
1851      * @param string $table_name
1852      * @param string $remove_column    When we drop a column, we remove the column
1853      *                                from all indexes. If the index has no other
1854      *                                column, we drop it completly.
1855      * @return array
1856      */
1857      protected function sqlite_get_recreate_table_queries($table_name, $remove_column = '')
1858      {
1859          $queries = array();
1860   
1861          $sql = "SELECT sql
1862              FROM sqlite_master
1863              WHERE type = 'table'
1864                  AND name = '{$table_name}'";
1865          $result = $this->db->sql_query($sql);
1866          $sql_create_table = $this->db->sql_fetchfield('sql');
1867          $this->db->sql_freeresult($result);
1868   
1869          if (!$sql_create_table)
1870          {
1871              return array();
1872          }
1873          $queries[] = $sql_create_table;
1874   
1875          $sql = "SELECT sql
1876              FROM sqlite_master
1877              WHERE type = 'index'
1878                  AND tbl_name = '{$table_name}'";
1879          $result = $this->db->sql_query($sql);
1880          while ($sql_create_index = $this->db->sql_fetchfield('sql'))
1881          {
1882              if ($remove_column)
1883              {
1884                  $match = array();
1885                  preg_match('#(?:[\w ]+)\((.*)\)#', $sql_create_index, $match);
1886                  if (!isset($match[1]))
1887                  {
1888                      continue;
1889                  }
1890   
1891                  // Find and remove $remove_column from the index
1892                  $columns = explode(', ', $match[1]);
1893                  $found_column = array_search($remove_column, $columns);
1894                  if ($found_column !== false)
1895                  {
1896                      unset($columns[$found_column]);
1897   
1898                      // If the column list is not empty add the index to the list
1899                      if (!empty($columns))
1900                      {
1901                          $queries[] = str_replace($match[1], implode(', ', $columns), $sql_create_index);
1902                      }
1903                  }
1904                  else
1905                  {
1906                      $queries[] = $sql_create_index;
1907                  }
1908              }
1909              else
1910              {
1911                  $queries[] = $sql_create_index;
1912              }
1913          }
1914          $this->db->sql_freeresult($result);
1915   
1916          return $queries;
1917      }
1918  }
1919