Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 557
mssql
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 23
10100.00
0.00% covered (danger)
0.00%
0 / 557
 get_dbms_type_map
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 64
 __construct
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 13
 sql_list_tables
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 14
 sql_create_table
0.00% covered (danger)
0.00%
0 / 1
462.00
0.00% covered (danger)
0.00%
0 / 74
 sql_list_columns
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 15
 sql_index_exists
0.00% covered (danger)
0.00%
0 / 1
20.00
0.00% covered (danger)
0.00%
0 / 17
 sql_unique_index_exists
0.00% covered (danger)
0.00%
0 / 1
20.00
0.00% covered (danger)
0.00%
0 / 17
 sql_prepare_column_data
0.00% covered (danger)
0.00%
0 / 1
156.00
0.00% covered (danger)
0.00%
0 / 46
 sql_column_add
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 6
 sql_column_remove
0.00% covered (danger)
0.00%
0 / 1
42.00
0.00% covered (danger)
0.00%
0 / 35
 sql_index_drop
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 5
 sql_table_drop
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 9
 sql_create_primary_key
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 9
 sql_create_unique_index
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 9
 sql_create_index
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 7
 get_max_index_name_length
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 10
 sql_list_index
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 14
 sql_column_change
0.00% covered (danger)
0.00%
0 / 1
110.00
0.00% covered (danger)
0.00%
0 / 42
 mssql_get_drop_default_constraints_queries
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 30
 mssql_get_drop_default_primary_key_queries
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 16
 mssql_is_column_identity
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 16
 get_existing_indexes
0.00% covered (danger)
0.00%
0 / 1
182.00
0.00% covered (danger)
0.00%
0 / 78
 mssql_is_sql_server_2000
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 11
<?php
/**
 *
 * This file is part of the phpBB Forum Software package.
 *
 * @copyright (c) phpBB Limited <https://www.phpbb.com>
 * @license GNU General Public License, version 2 (GPL-2.0)
 *
 * For full copyright and license information, please see
 * the docs/CREDITS.txt file.
 *
 */
namespace phpbb\db\tools;
/**
 * Database Tools for handling cross-db actions such as altering columns, etc.
 * Currently not supported is returning SQL for creating tables.
 */
class mssql extends tools
{
    /**
     * Is the used MS SQL Server a SQL Server 2000?
     * @var bool
     */
    protected $is_sql_server_2000;
    /**
     * Get the column types for mssql based databases
     *
     * @return array
     */
    static public function get_dbms_type_map()
    {
        return array(
            'mssql'        => array(
                'INT:'        => '[int]',
                'BINT'        => '[float]',
                'ULINT'        => '[int]',
                'UINT'        => '[int]',
                'UINT:'        => '[int]',
                'TINT:'        => '[int]',
                'USINT'        => '[int]',
                'BOOL'        => '[int]',
                'VCHAR'        => '[varchar] (255)',
                'VCHAR:'    => '[varchar] (%d)',
                'CHAR:'        => '[char] (%d)',
                'XSTEXT'    => '[varchar] (1000)',
                'STEXT'        => '[varchar] (3000)',
                'TEXT'        => '[varchar] (8000)',
                'MTEXT'        => '[text]',
                'XSTEXT_UNI'=> '[nvarchar] (100)',
                'STEXT_UNI'    => '[nvarchar] (255)',
                'TEXT_UNI'    => '[nvarchar] (4000)',
                'MTEXT_UNI'    => '[ntext]',
                'TIMESTAMP'    => '[int]',
                'DECIMAL'    => '[float]',
                'DECIMAL:'    => '[float]',
                'PDECIMAL'    => '[float]',
                'PDECIMAL:'    => '[float]',
                'VCHAR_UNI'    => '[nvarchar] (255)',
                'VCHAR_UNI:'=> '[nvarchar] (%d)',
                'VCHAR_CI'    => '[nvarchar] (255)',
                'VARBINARY'    => '[varchar] (255)',
            ),
            'mssqlnative'    => array(
                'INT:'        => '[int]',
                'BINT'        => '[float]',
                'ULINT'        => '[int]',
                'UINT'        => '[int]',
                'UINT:'        => '[int]',
                'TINT:'        => '[int]',
                'USINT'        => '[int]',
                'BOOL'        => '[int]',
                'VCHAR'        => '[varchar] (255)',
                'VCHAR:'    => '[varchar] (%d)',
                'CHAR:'        => '[char] (%d)',
                'XSTEXT'    => '[varchar] (1000)',
                'STEXT'        => '[varchar] (3000)',
                'TEXT'        => '[varchar] (8000)',
                'MTEXT'        => '[text]',
                'XSTEXT_UNI'=> '[nvarchar] (100)',
                'STEXT_UNI'    => '[nvarchar] (255)',
                'TEXT_UNI'    => '[nvarchar] (4000)',
                'MTEXT_UNI'    => '[ntext]',
                'TIMESTAMP'    => '[int]',
                'DECIMAL'    => '[float]',
                'DECIMAL:'    => '[float]',
                'PDECIMAL'    => '[float]',
                'PDECIMAL:'    => '[float]',
                'VCHAR_UNI'    => '[nvarchar] (255)',
                'VCHAR_UNI:'=> '[nvarchar] (%d)',
                'VCHAR_CI'    => '[nvarchar] (255)',
                'VARBINARY'    => '[varchar] (255)',
            ),
        );
    }
    /**
    * Constructor. Set DB Object and set {@link $return_statements return_statements}.
    *
    * @param \phpbb\db\driver\driver_interface    $db                    Database connection
    * @param bool        $return_statements    True if only statements should be returned and no SQL being executed
    */
    public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false)
    {
        parent::__construct($db, $return_statements);
        // Determine mapping database type
        switch ($this->db->get_sql_layer())
        {
            case 'mssql_odbc':
                $this->sql_layer = 'mssql';
            break;
            case 'mssqlnative':
                $this->sql_layer = 'mssqlnative';
            break;
        }
        $this->dbms_type_map = self::get_dbms_type_map();
    }
    /**
     * {@inheritDoc}
     */
    function sql_list_tables()
    {
        $sql = "SELECT name
            FROM sysobjects
            WHERE type='U'";
        $result = $this->db->sql_query($sql);
        $tables = array();
        while ($row = $this->db->sql_fetchrow($result))
        {
            $name = current($row);
            $tables[$name] = $name;
        }
        $this->db->sql_freeresult($result);
        return $tables;
    }
    /**
     * {@inheritDoc}
     */
    function sql_create_table($table_name, $table_data)
    {
        // holds the DDL for a column
        $columns = $statements = array();
        if ($this->sql_table_exists($table_name))
        {
            return $this->_sql_run_sql($statements);
        }
        // Begin transaction
        $statements[] = 'begin';
        // Determine if we have created a PRIMARY KEY in the earliest
        $primary_key_gen = false;
        // Determine if the table requires a sequence
        $create_sequence = false;
        // Begin table sql statement
        $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n";
        if (!isset($table_data['PRIMARY_KEY']))
        {
            $table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment');
            $table_data['PRIMARY_KEY'] = 'mssqlindex';
        }
        // Iterate through the columns to create a table
        foreach ($table_data['COLUMNS'] as $column_name => $column_data)
        {
            // here lies an array, filled with information compiled on the column's data
            $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
            if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen"
            {
                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);
            }
            // here we add the definition of the new column to the list of columns
            $columns[] = "\t [{$column_name}" . $prepared_column['column_type_sql_default'];
            // see if we have found a primary key set due to a column definition if we have found it, we can stop looking
            if (!$primary_key_gen)
            {
                $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
            }
            // create sequence DDL based off of the existence of auto incrementing columns
            if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
            {
                $create_sequence = $column_name;
            }
        }
        // this makes up all the columns in the create table statement
        $table_sql .= implode(",\n", $columns);
        // Close the table for two DBMS and add to the statements
        $table_sql .= "\n);";
        $statements[] = $table_sql;
        // we have yet to create a primary key for this table,
        // this means that we can add the one we really wanted instead
        if (!$primary_key_gen)
        {
            // Write primary key
            if (isset($table_data['PRIMARY_KEY']))
            {
                if (!is_array($table_data['PRIMARY_KEY']))
                {
                    $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
                }
                // We need the data here
                $old_return_statements = $this->return_statements;
                $this->return_statements = true;
                $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']);
                foreach ($primary_key_stmts as $pk_stmt)
                {
                    $statements[] = $pk_stmt;
                }
                $this->return_statements = $old_return_statements;
            }
        }
        // Write Keys
        if (isset($table_data['KEYS']))
        {
            foreach ($table_data['KEYS'] as $key_name => $key_data)
            {
                if (!is_array($key_data[1]))
                {
                    $key_data[1] = array($key_data[1]);
                }
                $old_return_statements = $this->return_statements;
                $this->return_statements = true;
                $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]);
                foreach ($key_stmts as $key_stmt)
                {
                    $statements[] = $key_stmt;
                }
                $this->return_statements = $old_return_statements;
            }
        }
        // Commit Transaction
        $statements[] = 'commit';
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritDoc}
     */
    function sql_list_columns($table_name)
    {
        $columns = array();
        $sql = "SELECT c.name
            FROM syscolumns c
            LEFT JOIN sysobjects o ON c.id = o.id
            WHERE o.name = '{$table_name}'";
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            $column = strtolower(current($row));
            $columns[$column] = $column;
        }
        $this->db->sql_freeresult($result);
        return $columns;
    }
    /**
     * {@inheritDoc}
     */
    function sql_index_exists($table_name, $index_name)
    {
        $sql = "EXEC sp_statistics '$table_name'";
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            if ($row['TYPE'] == 3)
            {
                if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
                {
                    $this->db->sql_freeresult($result);
                    return true;
                }
            }
        }
        $this->db->sql_freeresult($result);
        return false;
    }
    /**
     * {@inheritDoc}
     */
    function sql_unique_index_exists($table_name, $index_name)
    {
        $sql = "EXEC sp_statistics '$table_name'";
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            // Usually NON_UNIQUE is the column we want to check, but we allow for both
            if ($row['TYPE'] == 3)
            {
                if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
                {
                    $this->db->sql_freeresult($result);
                    return true;
                }
            }
        }
        $this->db->sql_freeresult($result);
        return false;
    }
    /**
     * {@inheritDoc}
    */
    function sql_prepare_column_data($table_name, $column_name, $column_data)
    {
        if (strlen($column_name) > 30)
        {
            trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
        }
        // Get type
        list($column_type, ) = $this->get_column_type($column_data[0]);
        // Adjust default value if db-dependent specified
        if (is_array($column_data[1]))
        {
            $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
        }
        $sql = '';
        $return_array = array();
        $sql .= " {$column_type} ";
        $sql_default = " {$column_type} ";
        // For adding columns we need the default definition
        if (!is_null($column_data[1]))
        {
            // For hexadecimal values do not use single quotes
            if (strpos($column_data[1], '0x') === 0)
            {
                $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') ';
                $sql_default .= $return_array['default'];
            }
            else
            {
                $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') ';
                $sql_default .= $return_array['default'];
            }
        }
        if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
        {
            // $sql .= 'IDENTITY (1, 1) ';
            $sql_default .= 'IDENTITY (1, 1) ';
        }
        $return_array['textimage'] = $column_type === '[text]';
        if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment'))
        {
            $sql .= 'NOT NULL';
            $sql_default .= 'NOT NULL';
        }
        else
        {
            $sql .= 'NULL';
            $sql_default .= 'NULL';
        }
        $return_array['column_type_sql_default'] = $sql_default;
        $return_array['column_type_sql'] = $sql;
        return $return_array;
    }
    /**
     * {@inheritDoc}
     */
    function sql_column_add($table_name, $column_name, $column_data, $inline = false)
    {
        $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
        $statements = array();
        // Does not support AFTER, only through temporary table
        $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default'];
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritDoc}
     */
    function sql_column_remove($table_name, $column_name, $inline = false)
    {
        $statements = array();
        // We need the data here
        $old_return_statements = $this->return_statements;
        $this->return_statements = true;
        $indexes = $this->get_existing_indexes($table_name, $column_name);
        $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true));
        // Drop any indexes
        $recreate_indexes = array();
        if (!empty($indexes))
        {
            foreach ($indexes as $index_name => $index_data)
            {
                $result = $this->sql_index_drop($table_name, $index_name);
                $statements = array_merge($statements, $result);
                if (count($index_data) > 1)
                {
                    // Remove this column from the index and recreate it
                    $recreate_indexes[$index_name] = array_diff($index_data, array($column_name));
                }
            }
        }
        // Drop primary keys depending on this column
        $result = $this->mssql_get_drop_default_primary_key_queries($table_name, $column_name);
        $statements = array_merge($statements, $result);
        // Drop default value constraint
        $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name);
        $statements = array_merge($statements, $result);
        // Remove the column
        $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
        if (!empty($recreate_indexes))
        {
            // Recreate indexes after we removed the column
            foreach ($recreate_indexes as $index_name => $index_data)
            {
                $result = $this->sql_create_index($table_name, $index_name, $index_data);
                $statements = array_merge($statements, $result);
            }
        }
        $this->return_statements = $old_return_statements;
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritDoc}
     */
    function sql_index_drop($table_name, $index_name)
    {
        $statements = array();
        $statements[] = 'DROP INDEX [' . $table_name . '].[' . $index_name . ']';
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritDoc}
     */
    function sql_table_drop($table_name)
    {
        $statements = array();
        if (!$this->sql_table_exists($table_name))
        {
            return $this->_sql_run_sql($statements);
        }
        // the most basic operation, get rid of the table
        $statements[] = 'DROP TABLE ' . $table_name;
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritDoc}
     */
    function sql_create_primary_key($table_name, $column, $inline = false)
    {
        $statements = array();
        $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD ";
        $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY  CLUSTERED (";
        $sql .= '[' . implode("],\n\t\t[", $column) . ']';
        $sql .= ')';
        $statements[] = $sql;
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritDoc}
     */
    function sql_create_unique_index($table_name, $index_name, $column)
    {
        $statements = array();
        if ($this->mssql_is_sql_server_2000())
        {
            $this->check_index_name_length($table_name, $index_name);
        }
        $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])';
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritDoc}
     */
    function sql_create_index($table_name, $index_name, $column)
    {
        $statements = array();
        $this->check_index_name_length($table_name, $index_name);
        // remove index length
        $column = preg_replace('#:.*$#', '', $column);
        $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])';
        return $this->_sql_run_sql($statements);
    }
    /**
     * {@inheritdoc}
     */
    protected function get_max_index_name_length()
    {
        if ($this->mssql_is_sql_server_2000())
        {
            return parent::get_max_index_name_length();
        }
        else
        {
            return 128;
        }
    }
    /**
     * {@inheritDoc}
     */
    function sql_list_index($table_name)
    {
        $index_array = array();
        $sql = "EXEC sp_statistics '$table_name'";
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            if ($row['TYPE'] == 3)
            {
                $index_array[] = strtolower($row['INDEX_NAME']);
            }
        }
        $this->db->sql_freeresult($result);
        return $index_array;
    }
    /**
     * {@inheritDoc}
     */
    function sql_column_change($table_name, $column_name, $column_data, $inline = false)
    {
        $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
        $statements = array();
        // We need the data here
        $old_return_statements = $this->return_statements;
        $this->return_statements = true;
        $indexes = $this->get_existing_indexes($table_name, $column_name);
        $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true);
        // Drop any indexes
        if (!empty($indexes) || !empty($unique_indexes))
        {
            $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes));
            foreach ($drop_indexes as $index_name)
            {
                $result = $this->sql_index_drop($table_name, $index_name);
                $statements = array_merge($statements, $result);
            }
        }
        // Drop default value constraint
        $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name);
        $statements = array_merge($statements, $result);
        // Change the column
        $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql'];
        if (!empty($column_data['default']) && !$this->mssql_is_column_identity($table_name, $column_name))
        {
            // Add new default value constraint
            $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']';
        }
        if (!empty($indexes))
        {
            // Recreate indexes after we changed the column
            foreach ($indexes as $index_name => $index_data)
            {
                $result = $this->sql_create_index($table_name, $index_name, $index_data);
                $statements = array_merge($statements, $result);
            }
        }
        if (!empty($unique_indexes))
        {
            // Recreate unique indexes after we changed the column
            foreach ($unique_indexes as $index_name => $index_data)
            {
                $result = $this->sql_create_unique_index($table_name, $index_name, $index_data);
                $statements = array_merge($statements, $result);
            }
        }
        $this->return_statements = $old_return_statements;
        return $this->_sql_run_sql($statements);
    }
    /**
    * Get queries to drop the default constraints of a column
    *
    * We need to drop the default constraints of a column,
    * before being able to change their type or deleting them.
    *
    * @param string $table_name
    * @param string $column_name
    * @return array        Array with SQL statements
    */
    protected function mssql_get_drop_default_constraints_queries($table_name, $column_name)
    {
        $statements = array();
        if ($this->mssql_is_sql_server_2000())
        {
            // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
            // Deprecated in SQL Server 2005
            $sql = "SELECT so.name AS def_name
                FROM sysobjects so
                JOIN sysconstraints sc ON so.id = sc.constid
                WHERE object_name(so.parent_obj) = '{$table_name}'
                    AND so.xtype = 'D'
                    AND sc.colid = (SELECT colid FROM syscolumns
                        WHERE id = object_id('{$table_name}')
                            AND name = '{$column_name}')";
        }
        else
        {
            $sql = "SELECT dobj.name AS def_name
                FROM sys.columns col
                    LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D')
                WHERE col.object_id = object_id('{$table_name}')
                    AND col.name = '{$column_name}'
                    AND dobj.name IS NOT NULL";
        }
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']';
        }
        $this->db->sql_freeresult($result);
        return $statements;
    }
    /**
     * Get queries to drop the primary keys depending on the specified column
     *
     * We need to drop primary keys depending on this column before being able
     * to delete them.
     *
     * @param string $table_name
     * @param string $column_name
     * @return array        Array with SQL statements
     */
    protected function mssql_get_drop_default_primary_key_queries($table_name, $column_name)
    {
        $statements = array();
        $sql = "SELECT ccu.CONSTRAINT_NAME, ccu.COLUMN_NAME
            FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
                JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.Constraint_name
            WHERE tc.TABLE_NAME = '{$table_name}'
                AND tc.CONSTRAINT_TYPE = 'Primary Key'
                AND ccu.COLUMN_NAME = '{$column_name}'";
        $result = $this->db->sql_query($sql);
        while ($primary_key = $this->db->sql_fetchrow($result))
        {
            $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $primary_key['CONSTRAINT_NAME'] . ']';
        }
        $this->db->sql_freeresult($result);
        return $statements;
    }
    /**
     * Checks to see if column is an identity column
     *
     * Identity columns cannot have defaults set for them.
     *
     * @param string $table_name
     * @param string $column_name
     * @return bool        true if identity, false if not
     */
    protected function mssql_is_column_identity($table_name, $column_name)
    {
        if ($this->mssql_is_sql_server_2000())
        {
            // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
            // Deprecated in SQL Server 2005
            $sql = "SELECT COLUMNPROPERTY(object_id('{$table_name}'), '{$column_name}', 'IsIdentity') AS is_identity";
        }
        else
        {
            $sql = "SELECT is_identity FROM sys.columns
                    WHERE object_id = object_id('{$table_name}')
                    AND name = '{$column_name}'";
        }
        $result = $this->db->sql_query($sql);
        $is_identity = $this->db->sql_fetchfield('is_identity');
        $this->db->sql_freeresult($result);
        return (bool) $is_identity;
    }
    /**
    * Get a list with existing indexes for the column
    *
    * @param string $table_name
    * @param string $column_name
    * @param bool $unique Should we get unique indexes or normal ones
    * @return array        Array with Index name => columns
    */
    public function get_existing_indexes($table_name, $column_name, $unique = false)
    {
        $existing_indexes = array();
        if ($this->mssql_is_sql_server_2000())
        {
            // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
            // Deprecated in SQL Server 2005
            $sql = "SELECT DISTINCT ix.name AS phpbb_index_name
                FROM sysindexes ix
                INNER JOIN sysindexkeys ixc
                    ON ixc.id = ix.id
                        AND ixc.indid = ix.indid
                INNER JOIN syscolumns cols
                    ON cols.colid = ixc.colid
                        AND cols.id = ix.id
                WHERE ix.id = object_id('{$table_name}')
                    AND cols.name = '{$column_name}'
                    AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0');
        }
        else
        {
            $sql = "SELECT DISTINCT ix.name AS phpbb_index_name
                FROM sys.indexes ix
                INNER JOIN sys.index_columns ixc
                    ON ixc.object_id = ix.object_id
                        AND ixc.index_id = ix.index_id
                INNER JOIN sys.columns cols
                    ON cols.column_id = ixc.column_id
                        AND cols.object_id = ix.object_id
                WHERE ix.object_id = object_id('{$table_name}')
                    AND cols.name = '{$column_name}'
                    AND ix.is_primary_key = 0
                    AND ix.is_unique = " . ($unique ? '1' : '0');
        }
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE'))
            {
                $existing_indexes[$row['phpbb_index_name']] = array();
            }
        }
        $this->db->sql_freeresult($result);
        if (empty($existing_indexes))
        {
            return array();
        }
        if ($this->mssql_is_sql_server_2000())
        {
            $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name
                FROM sysindexes ix
                INNER JOIN sysindexkeys ixc
                    ON ixc.id = ix.id
                        AND ixc.indid = ix.indid
                INNER JOIN syscolumns cols
                    ON cols.colid = ixc.colid
                        AND cols.id = ix.id
                WHERE ix.id = object_id('{$table_name}')
                    AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes));
        }
        else
        {
            $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name
                FROM sys.indexes ix
                INNER JOIN sys.index_columns ixc
                    ON ixc.object_id = ix.object_id
                        AND ixc.index_id = ix.index_id
                INNER JOIN sys.columns cols
                    ON cols.column_id = ixc.column_id
                        AND cols.object_id = ix.object_id
                WHERE ix.object_id = object_id('{$table_name}')
                    AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes));
        }
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name'];
        }
        $this->db->sql_freeresult($result);
        return $existing_indexes;
    }
    /**
    * Is the used MS SQL Server a SQL Server 2000?
    *
    * @return bool
    */
    protected function mssql_is_sql_server_2000()
    {
        if ($this->is_sql_server_2000 === null)
        {
            $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version";
            $result = $this->db->sql_query($sql);
            $properties = $this->db->sql_fetchrow($result);
            $this->db->sql_freeresult($result);
            $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8';
        }
        return $this->is_sql_server_2000;
    }
}