Example #1
0
 /**
  * Returns the tables in the current database
  * 
  * @return array  The tables in the current database
  */
 public function getTables()
 {
     if ($this->tables !== NULL) {
         return $this->tables;
     }
     switch ($this->database->getType()) {
         case 'mssql':
             $sql = "SELECT\r\n\t\t\t\t\t\t\t\tTABLE_NAME\r\n\t\t\t\t\t\t\tFROM\r\n\t\t\t\t\t\t\t\tINFORMATION_SCHEMA.TABLES\r\n\t\t\t\t\t\t\tWHERE\r\n\t\t\t\t\t\t\t\tTABLE_NAME != 'sysdiagrams'\r\n\t\t\t\t\t\t\tORDER BY\r\n\t\t\t\t\t\t\t\tLOWER(TABLE_NAME)";
             break;
         case 'mysql':
             $sql = 'SHOW TABLES';
             break;
         case 'oracle':
             $sql = "SELECT\r\n\t\t\t\t\t\t\t\tLOWER(TABLE_NAME)\r\n\t\t\t\t\t\t\tFROM\r\n\t\t\t\t\t\t\t\tUSER_TABLES\r\n\t\t\t\t\t\t\tWHERE\r\n\t\t\t\t\t\t\t\tSUBSTR(TABLE_NAME, 1, 4) <> 'BIN\$'\r\n\t\t\t\t\t\t\tORDER BY\r\n\t\t\t\t\t\t\t\tTABLE_NAME ASC";
             break;
         case 'postgresql':
             $sql = "SELECT\r\n\t\t\t\t\t\t\t\t tablename\r\n\t\t\t\t\t\t\tFROM\r\n\t\t\t\t\t\t\t\t pg_tables\r\n\t\t\t\t\t\t\tWHERE\r\n\t\t\t\t\t\t\t\t tablename !~ '^(pg|sql)_'\r\n\t\t\t\t\t\t\tORDER BY\r\n\t\t\t\t\t\t\t\tLOWER(tablename)";
             break;
         case 'sqlite':
             $sql = "SELECT\r\n\t\t\t\t\t\t\t\tname\r\n\t\t\t\t\t\t\tFROM\r\n\t\t\t\t\t\t\t\tsqlite_master\r\n\t\t\t\t\t\t\tWHERE\r\n\t\t\t\t\t\t\t\ttype = 'table' AND\r\n\t\t\t\t\t\t\t\tname NOT LIKE 'sqlite_%'\r\n\t\t\t\t\t\t\tORDER BY\r\n\t\t\t\t\t\t\t\tname ASC";
             break;
     }
     $result = $this->database->query($sql);
     $this->tables = array();
     foreach ($result as $row) {
         $keys = array_keys($row);
         $this->tables[] = $row[$keys[0]];
     }
     if ($this->cache) {
         $this->cache->set($this->makeCachePrefix() . 'tables', $this->tables);
     }
     return $this->tables;
 }
Example #2
0
 /**
  * Returns the tables in the current database
  *
  * @param  boolean|string  $creation_order  `TRUE` to return in a valid table creation order, or a table name to return that table and any tables that depend on it, in table creation order
  * @return array  The tables in the current database, all converted to lowercase
  */
 public function getTables($creation_order = NULL)
 {
     if ($creation_order) {
         return $this->determineTableCreationOrder(is_bool($creation_order) ? NULL : $creation_order);
     }
     if ($this->tables !== NULL) {
         return $this->tables;
     }
     switch ($this->database->getType()) {
         case 'db2':
             $sql = "SELECT\n\t\t\t\t\t\t\tLOWER(RTRIM(TABSCHEMA)) AS \"schema\",\n\t\t\t\t\t\t\tLOWER(TABNAME) AS \"table\"\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\tSYSCAT.TABLES\n\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\tTYPE = 'T' AND\n\t\t\t\t\t\t\tTABSCHEMA != 'SYSIBM' AND\n\t\t\t\t\t\t\tDEFINER != 'SYSIBM' AND\n\t\t\t\t\t\t\tTABSCHEMA != 'SYSTOOLS' AND\n\t\t\t\t\t\t\tDEFINER != 'SYSTOOLS'\n\t\t\t\t\t\tORDER BY\n\t\t\t\t\t\t\tLOWER(TABNAME)";
             break;
         case 'mssql':
             $sql = "SELECT\n\t\t\t\t\t\t\tTABLE_SCHEMA AS \"schema\",\n\t\t\t\t\t\t\tTABLE_NAME AS \"table\"\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\tINFORMATION_SCHEMA.TABLES\n\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\tTABLE_NAME != 'sysdiagrams'\n\t\t\t\t\t\tORDER BY\n\t\t\t\t\t\t\tLOWER(TABLE_NAME)";
             break;
         case 'mysql':
             if (version_compare($this->database->getVersion(), 5, '<')) {
                 $sql = 'SHOW TABLES';
             } else {
                 $sql = "SHOW FULL TABLES WHERE table_type = 'BASE TABLE'";
             }
             break;
         case 'oracle':
             $sql = "SELECT\n\t\t\t\t\t\t\tLOWER(OWNER) AS \"SCHEMA\",\n\t\t\t\t\t\t\tLOWER(TABLE_NAME) AS \"TABLE\"\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\tALL_TABLES\n\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\tOWNER NOT IN (\n\t\t\t\t\t\t\t\t'SYS',\n\t\t\t\t\t\t\t\t'SYSTEM',\n\t\t\t\t\t\t\t\t'OUTLN',\n\t\t\t\t\t\t\t\t'ANONYMOUS',\n\t\t\t\t\t\t\t\t'AURORA\$ORB\$UNAUTHENTICATED',\n\t\t\t\t\t\t\t\t'AWR_STAGE',\n\t\t\t\t\t\t\t\t'CSMIG',\n\t\t\t\t\t\t\t\t'CTXSYS',\n\t\t\t\t\t\t\t\t'DBSNMP',\n\t\t\t\t\t\t\t\t'DIP',\n\t\t\t\t\t\t\t\t'DMSYS',\n\t\t\t\t\t\t\t\t'DSSYS',\n\t\t\t\t\t\t\t\t'EXFSYS',\n\t\t\t\t\t\t\t\t'FLOWS_020100',\n\t\t\t\t\t\t\t\t'FLOWS_FILES',\n\t\t\t\t\t\t\t\t'LBACSYS',\n\t\t\t\t\t\t\t\t'MDSYS',\n\t\t\t\t\t\t\t\t'ORACLE_OCM',\n\t\t\t\t\t\t\t\t'ORDPLUGINS',\n\t\t\t\t\t\t\t\t'ORDSYS',\n\t\t\t\t\t\t\t\t'PERFSTAT',\n\t\t\t\t\t\t\t\t'TRACESVR',\n\t\t\t\t\t\t\t\t'TSMSYS',\n\t\t\t\t\t\t\t\t'XDB'\n\t\t\t\t\t\t\t) AND\n\t\t\t\t\t\t\tDROPPED = 'NO'\n\t\t\t\t\t\tORDER BY\n\t\t\t\t\t\t\tTABLE_NAME ASC";
             break;
         case 'postgresql':
             $sql = "SELECT\n\t\t\t\t\t\t\t schemaname AS \"schema\",\n\t\t\t\t\t\t\t tablename as \"table\"\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\t pg_tables\n\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\t tablename !~ '^(pg|sql)_'\n\t\t\t\t\t\tORDER BY\n\t\t\t\t\t\t\tLOWER(tablename)";
             break;
         case 'sqlite':
             $sql = "SELECT\n\t\t\t\t\t\t\tname\n\t\t\t\t\t\tFROM\n\t\t\t\t\t\t\tsqlite_master\n\t\t\t\t\t\tWHERE\n\t\t\t\t\t\t\ttype = 'table' AND\n\t\t\t\t\t\t\tname NOT LIKE 'sqlite_%'\n\t\t\t\t\t\tORDER BY\n\t\t\t\t\t\t\tname ASC";
             break;
     }
     $result = $this->database->query($sql);
     $this->tables = array();
     // For databases with schemas we only include the schema
     // name if there are conflicting table names
     if (!in_array($this->database->getType(), array('mysql', 'sqlite'))) {
         $default_schema_map = array('db2' => strtolower($this->database->getUsername()), 'mssql' => 'dbo', 'oracle' => strtolower($this->database->getUsername()), 'postgresql' => 'public');
         $default_schema = $default_schema_map[$this->database->getType()];
         foreach ($result as $row) {
             if ($row['schema'] == $default_schema) {
                 $this->tables[] = strtolower($row['table']);
             } else {
                 $this->tables[] = strtolower($row['schema'] . '.' . $row['table']);
             }
         }
         // SQLite and MySQL don't support schemas
     } else {
         foreach ($result as $row) {
             $keys = array_keys($row);
             $this->tables[] = strtolower($row[$keys[0]]);
         }
     }
     sort($this->tables);
     if ($this->cache) {
         $this->cache->set($this->makeCachePrefix() . 'tables', $this->tables);
     }
     return $this->tables;
 }
 /**
  * Translates Flourish SQL `ALTER TABLE` statements to the appropriate
  * statements for SQLite
  *
  * @param string $sql                The SQL statements that will be executed against the database
  * @param array  &$extra_statements  Any extra SQL statements required for SQLite
  * @param array  $data               Data parsed from the `ALTER TABLE` statement
  * @return string  The modified SQL statement
  */
 private function translateSQLiteAlterTableStatements($sql, &$extra_statements, $data)
 {
     $toggle_foreign_key_support = FALSE;
     if (!isset($this->schema_info['foreign_keys_enabled'])) {
         $toggle_foreign_key_support = TRUE;
         $foreign_keys_res = $this->database->query('PRAGMA foreign_keys');
         if ($foreign_keys_res->countReturnedRows() && $foreign_keys_res->fetchScalar()) {
             $this->schema_info['foreign_keys_enabled'] = TRUE;
             $this->database->query("PRAGMA foreign_keys = 0");
         } else {
             $this->schema_info['foreign_keys_enabled'] = FALSE;
         }
     }
     $temp_create_table_sql = $this->getSQLiteCreateTable($data['table']);
     if ($temp_create_table_sql === NULL) {
         $this->throwException(self::compose('The table "%1$s" does not exist', $data['table']), $sql);
     }
     $temp_create_table_sql = preg_replace('#(^\\s*CREATE\\s+TABLE\\s+)(?:`|\'|"|\\[)?(\\w+)(?:`|\'|"|\\])?(\\s*\\()#i', '\\1"fl_tmp_' . $data['table'] . '"\\3', $temp_create_table_sql);
     if ($data['type'] == 'drop_primary_key' && !preg_match('#\\bPRIMARY\\s+KEY\\b#', $temp_create_table_sql)) {
         $this->throwException(self::compose('The table "%1$s" does not have a primary key constraint', $data['table']), $sql);
     }
     if ($data['type'] == 'add_primary_key' && preg_match('#\\bPRIMARY\\s+KEY\\b#', $temp_create_table_sql)) {
         $this->throwException(self::compose('The table "%1$s" already has a primary key constraint', $data['table']), $sql);
     }
     if ($data['type'] == 'drop_unique') {
         $dropped_unique = FALSE;
     }
     if ($data['type'] == 'drop_foreign_key') {
         $dropped_foreign_key = FALSE;
     }
     if (in_array($data['type'], array('column_comment', 'alter_type', 'set_not_null', 'drop_not_null', 'drop_default', 'set_default', 'drop_primary_key', 'add_primary_key', 'drop_foreign_key', 'add_foreign_key', 'drop_unique', 'add_unique', 'set_check_constraint', 'drop_check_constraint'))) {
         $column_info = self::parseSQLiteColumnDefinitions($temp_create_table_sql);
         if (isset($data['column_name']) && !isset($column_info[$data['column_name']])) {
             $this->throwException(self::compose('The column "%1$s" does not exist in the table "%2$s"', $data['column_name'], $data['table']), $sql);
         }
         foreach ($column_info as $column => $info) {
             if (isset($data['column_name']) && $column == $data['column_name']) {
                 if ($data['type'] == 'alter_type') {
                     $info['pieces']['data_type'] = ' ' . $data['data_type'];
                 } elseif ($data['type'] == 'set_not_null') {
                     $info['pieces']['not_null'] = ' NOT NULL';
                     $info['pieces']['null'] = '';
                     if (isset($data['default'])) {
                         $info['pieces']['default'] = ' DEFAULT ' . $data['default'];
                     }
                 } elseif ($data['type'] == 'drop_not_null') {
                     $info['pieces']['not_null'] = '';
                     $info['pieces']['null'] = '';
                 } elseif ($data['type'] == 'set_default') {
                     $info['pieces']['default'] = ' DEFAULT ' . $data['default_value'];
                 } elseif ($data['type'] == 'drop_default') {
                     $info['pieces']['default'] = '';
                 } elseif ($data['type'] == 'set_check_constraint') {
                     $info['pieces']['check_constraint'] = $data['constraint'];
                 } elseif ($data['type'] == 'drop_check_constraint') {
                     $info['pieces']['check_constraint'] = '';
                 } elseif ($data['type'] == 'add_primary_key') {
                     $info['pieces']['primary_key'] = ' PRIMARY KEY' . (version_compare($this->database->getVersion(), 3, '>=') && $data['autoincrement'] ? ' AUTOINCREMENT' : '');
                 } elseif ($data['type'] == 'drop_foreign_key') {
                     if (trim($info['pieces']['foreign_key'])) {
                         $dropped_foreign_key = TRUE;
                     }
                     $info['pieces']['foreign_key'] = '';
                 } elseif ($data['type'] == 'add_foreign_key') {
                     $foreign_create_table = $this->getSQLiteCreateTable($data['foreign_table']);
                     if ($foreign_create_table === NULL) {
                         $this->throwException(self::compose('The referenced table "%1$s" does not exist', $data['foreign_table']), $sql);
                     }
                     $foreign_column_info = self::parseSQLiteColumnDefinitions($foreign_create_table);
                     if (!isset($foreign_column_info[$data['foreign_column']])) {
                         $this->throwException(self::compose('The referenced column "%1$s" does not exist in the referenced table "%2$s"', $data['foreign_column'], $data['foreign_table']), $sql);
                     }
                     $info['pieces']['foreign_key'] = ' REFERENCES ' . $data['references'];
                 } elseif ($data['type'] == 'drop_unique') {
                     if (trim($info['pieces']['unique'])) {
                         $dropped_unique = TRUE;
                     }
                     $info['pieces']['unique'] = '';
                 } elseif ($data['type'] == 'add_unique') {
                     $info['pieces']['unique'] = ' UNIQUE';
                 } elseif ($data['type'] == 'column_comment') {
                     if (preg_match('#(^\\s*,)|(^\\s*/\\*\\s*((?:(?!\\*/).)*?)\\s*\\*/\\s*,)#', $info['pieces']['comment/end'])) {
                         $info['pieces']['comment/end'] = ',';
                     }
                     $comment = str_replace("''", "'", substr($data['comment'], 1, -1));
                     if (strlen(trim($comment))) {
                         $info['pieces']['comment/end'] .= ' -- ' . $comment . "\n";
                     }
                 }
             } elseif ($data['type'] == 'drop_primary_key') {
                 if (trim($info['pieces']['primary_key'])) {
                     $data['column_name'] = $column;
                 }
                 $info['pieces']['not_null'] = ' NOT NULL';
                 $info['pieces']['primary_key'] = '';
             }
             $temp_create_table_sql = str_replace($info['definition'], join('', $info['pieces']), $temp_create_table_sql);
         }
     }
     $primary_key_regex = '#(?<=,|\\()\\s*(?:CONSTRAINT\\s+["`\\[]?\\w+["`\\]]?\\s+)?PRIMARY\\s+KEY\\s*\\(\\s*((?:\\s*[\'"`\\[]?\\w+[\'"`\\]]?\\s*,\\s*)*[\'"`\\[]?\\w+[\'"`\\]]?)\\s*\\)\\s*(?:,|\\s*(?=\\)))#mi';
     $foreign_key_regex = '#(?<=,|\\(|\\*/|\\n)(\\s*(?:CONSTRAINT\\s+["`\\[]?\\w+["`\\]]?\\s+)?FOREIGN\\s+KEY\\s*\\(?\\s*[\'"`\\[]?(\\w+)[\'"`\\]]?\\s*\\)?)\\s+REFERENCES\\s+[\'"`\\[]?(\\w+)[\'"`\\]]?\\s*\\(\\s*[\'"`\\[]?(\\w+)[\'"`\\]]?\\s*\\)\\s*(?:(?:\\s+ON\\s+DELETE\\s+(CASCADE|NO\\s+ACTION|RESTRICT|SET\\s+NULL|SET\\s+DEFAULT))|(?:\\s+ON\\s+UPDATE\\s+(CASCADE|NO\\s+ACTION|RESTRICT|SET\\s+NULL|SET\\s+DEFAULT)))*(?:\\s+(?:DEFERRABLE|NOT\\s+DEFERRABLE))?\\s*(?:,(?:[ \\t]*--[^\\n]*\\n)?|(?:--[^\\n]*\\n)?\\s*(?=\\)))#mis';
     $unique_constraint_regex = '#(?<=,|\\()\\s*(?:CONSTRAINT\\s+["`\\[]?\\w+["`\\]]?\\s+)?UNIQUE\\s*\\(\\s*((?:\\s*[\'"`\\[]?\\w+[\'"`\\]]?\\s*,\\s*)*[\'"`\\[]?\\w+[\'"`\\]]?)\\s*\\)\\s*(?:,|\\s*(?=\\)))#mi';
     if (isset($data['column_name'])) {
         $column_regex = '#(?:`|\'|"|\\[|\\b)' . preg_quote($data['column_name'], '#') . '(?:`|\'|"|\\]|\\b)#i';
     }
     if ($data['type'] == 'drop_primary_key' || $data['type'] == 'drop_column') {
         // Drop any table-level primary keys
         preg_match_all($primary_key_regex, $temp_create_table_sql, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
             $columns = preg_split('#[\'"`\\]]?\\s*,\\s*[\'"`\\[]?#', strtolower(trim($match[1], '\'[]`"')));
             sort($columns);
             if ($data['type'] == 'drop_primary_key' && count($columns) == 1) {
                 $data['column_name'] = reset($columns);
             } elseif ($data['type'] == 'drop_column') {
                 if (!in_array($data['column_name'], $columns)) {
                     continue;
                 }
             }
             $temp_create_table_sql = self::removeFromSQLiteCreateTable($temp_create_table_sql, $match[0]);
         }
     }
     if ($data['type'] == 'drop_foreign_key' || $data['type'] == 'drop_column') {
         // Drop any table-level foreign keys
         preg_match_all($foreign_key_regex, $temp_create_table_sql, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
             // Ignore foreign keys for other columns
             if (strtolower($match[2]) != $data['column_name']) {
                 continue;
             }
             $dropped_foreign_key = TRUE;
             $temp_create_table_sql = self::removeFromSQLiteCreateTable($temp_create_table_sql, $match[0]);
         }
     }
     if ($data['type'] == 'drop_foreign_key' && !$dropped_foreign_key) {
         $this->throwException(self::compose('The column "%1$s" in the table "%2$s" does not have a foreign key constraint', $data['column_name'], $data['table']), $sql);
     }
     if ($data['type'] == 'drop_unique' || $data['type'] == 'drop_column') {
         // Drop any table-level unique keys
         preg_match_all($unique_constraint_regex, $temp_create_table_sql, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
             $columns = preg_split('#[\'"`\\]]?\\s*,\\s*[\'"`\\[]?#', strtolower(trim($match[1], '\'[]`"')));
             sort($columns);
             if ($data['type'] == 'drop_column') {
                 if (!in_array($data['column_name'], $columns)) {
                     continue;
                 }
             } else {
                 sort($data['column_names']);
                 if ($columns != $data['column_names']) {
                     continue;
                 }
                 $dropped_unique = TRUE;
             }
             $temp_create_table_sql = self::removeFromSQLiteCreateTable($temp_create_table_sql, $match[0]);
         }
     }
     if ($data['type'] == 'rename_column') {
         // Rename the column in the column definition and check constraint
         $column_info = self::parseSQLiteColumnDefinitions($temp_create_table_sql);
         if (!isset($column_info[$data['column_name']])) {
             $this->throwException(self::compose('The column "%1$s" does not exist in the table "%2$s"', $data['column_name'], $data['table']), $sql);
         }
         if (isset($column_info[$data['new_column_name']])) {
             $this->throwException(self::compose('The column "%1$s" already exists in the table "%2$s"', $data['new_column_name'], $data['table']), $sql);
         }
         $info = $column_info[$data['column_name']];
         $temp_create_table_sql = str_replace($info['definition'], preg_replace('#^(\\s*)[`"\'\\[]?' . preg_quote($data['column_name'], '#') . '[`"\'\\]]?(\\s+)#i', '\\1"' . $data['new_column_name'] . '"\\2', $info['definition']), $temp_create_table_sql);
         if ($info['pieces']['check_constraint']) {
             $temp_create_table_sql = str_replace($info['pieces']['check_constraint'], preg_replace('#^(\\s*CHECK\\s*\\(\\s*)[`"\'\\[]?' . preg_quote($data['column_name'], '#') . '[`"\'\\]]?(\\s+)#i', '\\1"' . $data['new_column_name'] . '"\\2', $info['pieces']['check_constraint']), $temp_create_table_sql);
         }
         // Rename the column in table-level primary key
         preg_match_all($primary_key_regex, $temp_create_table_sql, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
             $temp_create_table_sql = str_replace($match[0], preg_replace($column_regex, '"' . $data['new_column_name'] . '"', $match[0]), $temp_create_table_sql);
         }
         // Rename the column in table-level foreign key definitions
         preg_match_all($foreign_key_regex, $temp_create_table_sql, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
             $temp_create_table_sql = str_replace($match[0], str_replace($match[1], preg_replace($column_regex, '"' . $data['new_column_name'] . '"', $match[1]), $match[0]), $temp_create_table_sql);
         }
         // Rename the column in table-level unique constraints
         preg_match_all($unique_constraint_regex, $temp_create_table_sql, $matches, PREG_SET_ORDER);
         foreach ($matches as $match) {
             $temp_create_table_sql = str_replace($match[0], preg_replace($column_regex, '"' . $data['new_column_name'] . '"', $match[0]), $temp_create_table_sql);
         }
     }
     if ($data['type'] == 'drop_column') {
         $column_info = self::parseSQLiteColumnDefinitions($temp_create_table_sql);
         if (!isset($column_info[$data['column_name']])) {
             $this->throwException(self::compose('The column "%1$s" does not exist in the table "%2$s"', $data['column_name'], $data['table']), $sql);
         }
         $temp_create_table_sql = self::removeFromSQLiteCreateTable($temp_create_table_sql, $column_info[$data['column_name']]['definition']);
     }
     if ($data['type'] == 'add_primary_key' && count($data['column_names']) > 1) {
         $temp_create_table_sql = preg_replace('#\\s*\\)\\s*$#D', $this->database->escape(",\n    PRIMARY KEY(%r)\n)", $data['column_names']), $temp_create_table_sql);
     }
     if ($data['type'] == 'add_unique' && count($data['column_names']) > 1) {
         $temp_create_table_sql = preg_replace('#\\s*\\)\\s*$#D', $this->database->escape(",\n    UNIQUE(%r)\n)", $data['column_names']), $temp_create_table_sql);
     }
     if ($data['type'] == 'add_column') {
         if (!preg_match('#\\s*"?\\w+"?\\s+\\w+#', $data['column_definition'])) {
             $this->throwException(self::compose('Please specify a data type for the column "%1$s"', $data['column_name']), $sql);
         }
         preg_match('#^(.*?)((?:(?<=,|\\*/|\\n)\\s*(?:CONSTRAINT\\s+["`\\[]?\\w+["`\\]]?\\s+)?\\b(?:FOREIGN\\s+KEY\\b|PRIMARY\\s+KEY\\b|UNIQUE\\b).*)|(?:\\s*\\)\\s*))$#Dis', $temp_create_table_sql, $match);
         if (trim($match[2]) != ')') {
             $prefix = '';
             $suffix = ',';
         } else {
             $prefix = ',';
             $suffix = '';
         }
         // Be sure to add any necessary comma before a single-line SQL comment
         // because if it is placed after, the comma will be part of the comment
         if ($prefix) {
             $original_match_1 = $match[1];
             $match[1] = preg_replace('#(\\s*--[^\\n]+)(\\s*)?$#Di', ',\\1\\2', $match[1]);
             if ($match[1] != $original_match_1) {
                 $prefix = '';
             }
         }
         if (version_compare($this->database->getVersion(), 3, '>=')) {
             $data['column_definition'] = preg_replace('#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\s+primary\\s+key\\b#i', 'INTEGER PRIMARY KEY AUTOINCREMENT', $data['column_definition']);
             $data['column_definition'] = preg_replace("#datetime\\(\\s*CURRENT_TIMESTAMP\\s*,\\s*'localtime'\\s*\\)#i", 'CURRENT_TIMESTAMP', $data['column_definition']);
         } else {
             $data['column_definition'] = preg_replace('#\\binteger(?:\\(\\d+\\))?\\s+autoincrement\\s+primary\\s+key\\b#i', 'INTEGER PRIMARY KEY', $data['column_definition']);
             $data['column_definition'] = preg_replace('#CURRENT_TIMESTAMP\\(\\)#i', 'CURRENT_TIMESTAMP', $data['column_definition']);
         }
         $match[1] .= $prefix . "\n\t" . $data['column_definition'] . $suffix;
         $temp_create_table_sql = $match[1] . $match[2];
     }
     // Clean up extra line breaks
     $temp_create_table_sql = preg_replace('#\\n([ \\t]*\\n)+#', "\n", $temp_create_table_sql);
     // SQLite 3 supports renaming a table, so we need the full create
     // table with all of the translated triggers, etc
     if (version_compare($this->database->getVersion(), 3, '>=')) {
         // We rename string placeholders to prevent confusion with
         // string placeholders that are added by call to fDatabase
         $temp_create_table_sql = str_replace('%', '%%', $temp_create_table_sql);
         $extra_statements = array_merge($extra_statements, str_replace('%%', '%', $this->database->preprocess($temp_create_table_sql, array(), TRUE)));
         // For SQLite 2 we can't rename the table, so we end up needing to
         // create a new one so the temporary table doesn't need triggers
     } else {
         $extra_statements[] = $temp_create_table_sql;
     }
     $this->addSQLiteTable('fl_tmp_' . $data['table'], $temp_create_table_sql);
     // Next we copy the data from the original table to the temp table
     if ($data['type'] == 'rename_column') {
         $column_names = $this->getSQLiteColumns($data['table']);
         $new_column_names = $column_names;
         $column_position = array_search($data['column_name'], $new_column_names);
         $new_column_names[$column_position] = $data['new_column_name'];
     } elseif ($data['type'] == 'drop_column') {
         $column_names = array_diff($this->getSQLiteColumns($data['table']), array($data['column_name']));
         $new_column_names = $column_names;
     } else {
         $column_names = $this->getSQLiteColumns($data['table']);
         $new_column_names = $column_names;
     }
     $extra_statements[] = $this->database->escape("INSERT INTO %r (%r) SELECT %r FROM %r", 'fl_tmp_' . $data['table'], $new_column_names, $column_names, $data['table']);
     // Recreate the indexes for the temp table
     $indexes = $this->getSQLiteIndexes($data['table']);
     foreach ($indexes as $name => $index) {
         $create_sql = $index['sql'];
         preg_match('#^\\s*CREATE\\s+(UNIQUE\\s+)?INDEX\\s+(?:[\'"`\\[]?\\w+[\'"`\\]]?\\.)?[\'"`\\[]?\\w+[\'"`\\]]?\\s+(ON\\s+[\'"`\\[]?\\w+[\'"`\\]]?)\\s*(\\((\\s*(?:\\s*[\'"`\\[]?\\w+[\'"`\\]]?\\s*,\\s*)*[\'"`\\[]?\\w+[\'"`\\]]?\\s*)\\))\\s*$#Di', $create_sql, $match);
         $columns = preg_split('#[\'"`\\]]?\\s*,\\s*[\'"`\\[]?#', strtolower(trim($match[4], '\'[]`"')));
         if ($data['type'] == 'rename_column') {
             $create_sql = str_replace($match[3], preg_replace($column_regex, '"' . $data['new_column_name'] . '"', $match[3]), $create_sql);
         } elseif ($data['type'] == 'drop_column') {
             if (in_array($data['column_name'], $columns)) {
                 continue;
             }
         } elseif ($data['type'] == 'drop_unique') {
             sort($columns);
             sort($data['column_names']);
             if ($columns == $data['column_names']) {
                 $dropped_unique = TRUE;
                 continue;
             }
         }
         // The index needs to be altered to be created on the new table
         $create_sql = str_replace($match[2], preg_replace('#(?:`|\'|"|\\[|\\b)?' . preg_quote($data['table'], '#') . '(?:`|\'|"|\\]|\\b)?#', '"fl_tmp_' . $data['table'] . '"', $match[2]), $create_sql);
         // We fix the name of the index to keep in sync with the table name
         if (version_compare($this->database->getVersion(), 3, '>=')) {
             $new_name = $name;
         } else {
             $new_name = preg_replace('#^' . preg_quote($data['table'], '#') . '#', 'fl_tmp_' . $data['table'], $name);
         }
         // Ensure we have a unique index name
         while (isset($indexes[$new_name])) {
             if (preg_match('#(?<=_)(\\d+)$#D', $new_name, $match)) {
                 $new_name = preg_replace('#(?<=_)\\d+$#D', $match[1] + 1, $new_name);
             } else {
                 $new_name .= '_2';
             }
         }
         $create_sql = preg_replace('#[\'"`\\[]?' . preg_quote($name, '#') . '[\'"`\\]]?(\\s+ON\\s+)#i', '"' . $new_name . '"\\1', $create_sql);
         $this->addSQLiteIndex($new_name, 'fl_tmp_' . $data['table'], $create_sql);
         $extra_statements[] = $create_sql;
     }
     if ($data['type'] == 'drop_unique' && !$dropped_unique) {
         if (count($data['column_names']) > 1) {
             $message = self::compose('The columns "%1$s" in the table "%2$s" do not have a unique constraint', join('", "', $data['column_names']), $data['table']);
         } else {
             $message = self::compose('The column "%1$s" in the table "%2$s" does not have a unique constraint', reset($data['column_names']), $data['table']);
         }
         $this->throwException($message, $sql);
     }
     if (in_array($data['type'], array('rename_column', 'drop_column')) || $data['type'] == 'drop_primary_key' && isset($data['column_name'])) {
         $foreign_keys = $this->getSQLiteForeignKeys($data['table'], $data['column_name']);
         foreach ($foreign_keys as $key) {
             $extra_statements = array_merge($extra_statements, $this->database->preprocess("ALTER TABLE %r DROP FOREIGN KEY (%r)", array($key['table'], $key['column']), TRUE));
         }
     }
     // Drop the original table
     $extra_statements = array_merge($extra_statements, $this->database->preprocess("DROP TABLE %r", array($data['table']), TRUE));
     // Rename the temp table to the original name
     $extra_statements = array_merge($extra_statements, $this->database->preprocess("ALTER TABLE %r RENAME TO %r", array('fl_tmp_' . $data['table'], $data['table']), TRUE));
     // Re-add the foreign key constraints for renamed columns
     if ($data['type'] == 'rename_column') {
         foreach ($foreign_keys as $key) {
             $extra_statements = array_merge($extra_statements, $this->database->preprocess("ALTER TABLE %r ADD FOREIGN KEY (%r) REFERENCES %r(%r) ON UPDATE " . $key['on_update'] . " ON DELETE " . $key['on_delete'], array($key['table'], $key['column'], $data['table'], $data['new_column_name']), TRUE));
         }
     }
     // Finally, we turn back on foreign keys
     if ($toggle_foreign_key_support) {
         if ($this->schema_info['foreign_keys_enabled']) {
             $this->database->query("PRAGMA foreign_keys = 1");
         }
         unset($this->schema_info['foreign_keys_enabled']);
     }
     // Remove any nested transactions
     $extra_statements = array_diff($extra_statements, array("BEGIN", "COMMIT"));
     // Overwrite the original ALTER TABLE SQL
     $sql = array_shift($extra_statements);
     return $sql;
 }
 /**
  * Fixes pulling unicode data out of national data type MSSQL columns
  * 
  * @param  string $sql  The SQL to fix
  * @return string  The fixed SQL
  */
 private function fixMSSQLNationalColumns($sql)
 {
     if (!preg_match_all('#select((?:(?:(?!\\sfrom\\s)[^()])+|\\(((?:[^()]+|\\((?2)\\))*)\\))*\\s)from((?:(?:(?!\\sunion\\s|\\swhere\\s|\\sgroup by\\s|\\slimit\\s|\\sorder by\\s)[^()])+|\\(((?:[^()]+|\\((?4)\\))*)\\))*)(?=\\swhere\\s|\\sgroup by\\s|\\slimit\\s|\\sorder by\\s|\\sunion\\s|\\)|$)#i', $sql, $matches, PREG_SET_ORDER)) {
         return $sql;
     }
     if (!isset($this->schema_info['national_columns'])) {
         $result = $this->database->query("SELECT\n\t\t\t\t\t\tc.table_schema AS \"schema\",\n\t\t\t\t\t\tc.table_name   AS \"table\",\t\t\t\t\t\t\n\t\t\t\t\t\tc.column_name  AS \"column\",\n\t\t\t\t\t\tc.data_type    AS \"type\"\n\t\t\t\t\tFROM\n\t\t\t\t\t\tINFORMATION_SCHEMA.COLUMNS AS c\n\t\t\t\t\tWHERE\n\t\t\t\t\t\t(c.data_type = 'nvarchar' OR\n\t\t\t\t\t\t c.data_type = 'ntext' OR\n\t\t\t\t\t\t c.data_type = 'nchar') AND\n\t\t\t\t\t\tc.table_catalog = DB_NAME()\n\t\t\t\t\tORDER BY\n\t\t\t\t\t\tlower(c.table_name) ASC,\n\t\t\t\t\t\tlower(c.column_name) ASC");
         $national_columns = array();
         $national_types = array();
         foreach ($result as $row) {
             if (!isset($national_columns[$row['table']])) {
                 $national_columns[$row['table']] = array();
                 $national_types[$row['table']] = array();
                 $national_columns[$row['schema'] . '.' . $row['table']] = array();
                 $national_types[$row['schema'] . '.' . $row['table']] = array();
             }
             $national_columns[$row['table']][] = $row['column'];
             $national_types[$row['table']][$row['column']] = $row['type'];
             $national_columns[$row['schema'] . '.' . $row['table']][] = $row['column'];
             $national_types[$row['schema'] . '.' . $row['table']][$row['column']] = $row['type'];
         }
         $this->schema_info['national_columns'] = $national_columns;
         $this->schema_info['national_types'] = $national_types;
         if ($this->cache) {
             $this->cache->set($this->makeCachePrefix() . 'schema_info', $this->schema_info);
         }
     } else {
         $national_columns = $this->schema_info['national_columns'];
         $national_types = $this->schema_info['national_types'];
     }
     $additions = array();
     foreach ($matches as $select) {
         $select_clause = trim($select[1]);
         $from_clause = trim($select[3]);
         $sub_selects = array();
         if (preg_match_all('#\\((\\s*SELECT\\s+((?:[^()]+|\\((?2)\\))*))\\)#i', $from_clause, $from_matches)) {
             $sub_selects = $from_matches[0];
             foreach ($sub_selects as $i => $sub_select) {
                 $from_clause = preg_replace('#' . preg_quote($sub_select, '#') . '#', ':sub_select_' . $i, $from_clause, 1);
             }
         }
         $table_aliases = self::parseTableAliases($from_clause);
         preg_match_all('#([^,()]+|\\((?:(?1)|,)*\\))+#i', $select_clause, $selections);
         $selections = array_map('trim', $selections[0]);
         $to_fix = array();
         foreach ($selections as $selection) {
             // We just skip CASE statements since we can't really do those reliably
             if (preg_match('#^case#i', $selection)) {
                 continue;
             }
             if (preg_match('#(("?\\w+"?\\.)"?\\w+"?)\\.\\*#i', $selection, $match)) {
                 $match[1] = str_replace('"', '', $match[1]);
                 $table = $table_aliases[$match[1]];
                 if (empty($national_columns[$table])) {
                     continue;
                 }
                 if (!isset($to_fix[$table])) {
                     $to_fix[$table] = array();
                 }
                 $to_fix[$table] = array_merge($to_fix[$table], $national_columns[$table]);
             } elseif (preg_match('#\\*#', $selection, $match)) {
                 foreach ($table_aliases as $alias => $table) {
                     if (empty($national_columns[$table])) {
                         continue;
                     }
                     if (!isset($to_fix[$table])) {
                         $to_fix[$table] = array();
                     }
                     $to_fix[$table] = array_merge($to_fix[$table], $national_columns[$table]);
                 }
             } elseif (preg_match('#^(?:((?:"?\\w+"?\\.)?"?\\w+"?)\\.("?\\w+"?)|((?:min|max|trim|rtrim|ltrim|substring|replace)\\(((?:"?\\w+"?\\.)"?\\w+"?)\\.("?\\w+"?).*?\\)))(?:\\s+as\\s+("?\\w+"?))?$#iD', $selection, $match)) {
                 $table = $match[1] . (isset($match[4]) ? $match[4] : '');
                 $column = $match[2] . (isset($match[5]) ? $match[5] : '');
                 // Unquote identifiers
                 $table = str_replace('"', '', $table);
                 $column = str_replace('"', '', $column);
                 $table = $table_aliases[$table];
                 if (empty($national_columns[$table]) || !in_array($column, $national_columns[$table])) {
                     continue;
                 }
                 if (!isset($to_fix[$table])) {
                     $to_fix[$table] = array();
                 }
                 // Handle column aliasing
                 if (!empty($match[6])) {
                     $column = array('column' => $column, 'alias' => str_replace('"', '', $match[6]));
                 }
                 if (!empty($match[3])) {
                     if (!is_array($column)) {
                         $column = array('column' => $column);
                     }
                     $column['expression'] = $match[3];
                 }
                 $to_fix[$table] = array_merge($to_fix[$table], array($column));
                 // Match unqualified column names
             } elseif (preg_match('#^(?:("?\\w+"?)|((?:min|max|trim|rtrim|ltrim|substring|replace)\\(("?\\w+"?).*?\\)))(?:\\s+as\\s+("?\\w+"?))?$#iD', $selection, $match)) {
                 $column = $match[1] . (isset($match[3]) ? $match[3] : '');
                 // Unquote the identifiers
                 $column = str_replace('"', '', $column);
                 foreach ($table_aliases as $alias => $table) {
                     if (empty($national_columns[$table])) {
                         continue;
                     }
                     if (!in_array($column, $national_columns[$table])) {
                         continue;
                     }
                     if (!isset($to_fix[$table])) {
                         $to_fix[$table] = array();
                     }
                     // Handle column aliasing
                     if (!empty($match[4])) {
                         $column = array('column' => $column, 'alias' => str_replace('"', '', $match[4]));
                     }
                     if (!empty($match[2])) {
                         if (!is_array($column)) {
                             $column = array('column' => $column);
                         }
                         $column['expression'] = $match[2];
                     }
                     $to_fix[$table] = array_merge($to_fix[$table], array($column));
                 }
             }
         }
         $reverse_table_aliases = array_flip($table_aliases);
         foreach ($to_fix as $table => $columns) {
             $columns = array_unique($columns);
             $alias = $reverse_table_aliases[$table];
             foreach ($columns as $column) {
                 if (is_array($column)) {
                     if (isset($column['alias'])) {
                         $as = ' AS fmssqln__' . $column['alias'];
                     } else {
                         $as = ' AS fmssqln__' . $column['column'];
                     }
                     if (isset($column['expression'])) {
                         $expression = $column['expression'];
                     } else {
                         $expression = '"' . $alias . '"."' . $column['column'] . '"';
                     }
                     $column = $column['column'];
                 } else {
                     $as = ' AS fmssqln__' . $column;
                     $expression = '"' . $alias . '"."' . $column . '"';
                 }
                 if ($national_types[$table][$column] == 'ntext') {
                     $cast = 'CAST(' . $expression . ' AS IMAGE)';
                 } else {
                     $cast = 'CAST(' . $expression . ' AS VARBINARY(MAX))';
                 }
                 $additions[] = $cast . $as;
             }
         }
         foreach ($sub_selects as $i => $sub_select) {
             $sql = preg_replace('#:sub_select_' . $i . '\\b#', strtr($this->fixMSSQLNationalColumns($sub_select), array('\\' => '\\\\', '$' => '\\$')), $sql, 1);
         }
         $replace = preg_replace('#\\bselect\\s+' . preg_quote($select_clause, '#') . '#i', 'SELECT ' . strtr(join(', ', array_merge($selections, $additions)), array('\\' => '\\\\', '$' => '\\$')), $select);
         $sql = str_replace($select, $replace, $sql);
     }
     return $sql;
 }