Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

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