Index: program/lib/MDB2/Driver/Manager/mssql.php
===================================================================
--- program/lib/MDB2/Driver/Manager/mssql.php	(revision 95ebbc985a0e64a364e880de89dbc1ae7952d91e)
+++ program/lib/MDB2/Driver/Manager/mssql.php	(revision d1403fd7268ccf96ab6e7d04506ea1b1802c7eb2)
@@ -3,5 +3,5 @@
 // | PHP versions 4 and 5                                                 |
 // +----------------------------------------------------------------------+
-// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox,                 |
+// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,                 |
 // | Stig. S. Bakken, Lukas Smith                                         |
 // | All rights reserved.                                                 |
@@ -45,5 +45,5 @@
 // +----------------------------------------------------------------------+
 //
-// $Id: mssql.php,v 1.93 2007/12/03 20:59:15 quipo Exp $
+// $Id: mssql.php,v 1.109 2008/03/05 12:55:57 afz Exp $
 //
 
@@ -91,4 +91,37 @@
         }
         return $db->standaloneQuery($query, null, true);
+    }
+
+    // }}}
+    // {{{ alterDatabase()
+
+    /**
+     * alter an existing database
+     *
+     * @param string $name    name of the database that is intended to be changed
+     * @param array  $options array with name, collation info
+     *
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function alterDatabase($name, $options = array())
+    {
+        $db =& $this->getDBInstance();
+        if (PEAR::isError($db)) {
+            return $db;
+        }
+
+        $query = '';
+        if (!empty($options['name'])) {
+            $query .= ' MODIFY NAME = ' .$db->quoteIdentifier($options['name'], true);
+        }
+        if (!empty($options['collation'])) {
+            $query .= ' COLLATE ' . $options['collation'];
+        }
+        if (!empty($query)) {
+            $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true) . $query;
+            return $db->standaloneQuery($query, null, true);
+        }
+        return MDB2_OK;
     }
 
@@ -198,4 +231,62 @@
         }
         return parent::createTable($name, $fields, $options);
+    }
+
+    // }}}
+    // {{{ truncateTable()
+
+    /**
+     * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported,
+     * it falls back to a DELETE FROM TABLE query)
+     *
+     * @param string $name name of the table that should be truncated
+     * @return mixed MDB2_OK on success, a MDB2 error on failure
+     * @access public
+     */
+    function truncateTable($name)
+    {
+        $db =& $this->getDBInstance();
+        if (PEAR::isError($db)) {
+            return $db;
+        }
+
+        $name = $db->quoteIdentifier($name, true);
+        return $db->exec("TRUNCATE TABLE $name");
+    }
+
+    // }}}
+    // {{{ vacuum()
+
+    /**
+     * Optimize (vacuum) all the tables in the db (or only the specified table)
+     * and optionally run ANALYZE.
+     *
+     * @param string $table table name (all the tables if empty)
+     * @param array  $options an array with driver-specific options:
+     *               - timeout [int] (in seconds) [mssql-only]
+     *               - analyze [boolean] [pgsql and mysql]
+     *               - full [boolean] [pgsql-only]
+     *               - freeze [boolean] [pgsql-only]
+     *
+     * NB: you have to run the NSControl Create utility to enable VACUUM
+     *
+     * @return mixed MDB2_OK success, a MDB2 error on failure
+     * @access public
+     */
+    function vacuum($table = null, $options = array())
+    {
+        $db =& $this->getDBInstance();
+        if (PEAR::isError($db)) {
+            return $db;
+        }
+        $timeout = isset($options['timeout']) ? (int)$options['timeout'] : 300;
+
+        $query = 'NSControl Create';
+        $result = $db->exec($query);
+        if (PEAR::isError($result)) {
+            return $result;
+        }
+
+        return $db->exec('EXEC NSVacuum '.$timeout);
     }
 
@@ -299,14 +390,14 @@
             return $db;
         }
+        $name_quoted = $db->quoteIdentifier($name, true);
 
         foreach ($changes as $change_name => $change) {
             switch ($change_name) {
+            case 'remove':
+            case 'rename':
             case 'add':
+            case 'change':
+            case 'name':
                 break;
-            case 'remove':
-                break;
-            case 'name':
-            case 'rename':
-            case 'change':
             default:
                 return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null,
@@ -319,17 +410,20 @@
         }
 
-        $query = '';
-        if (!empty($changes['add']) && is_array($changes['add'])) {
-            foreach ($changes['add'] as $field_name => $field) {
-                if ($query) {
-                    $query.= ', ';
-                } else {
-                    $query.= 'ADD COLUMN ';
-                }
-                $query.= $db->getDeclaration($field['type'], $field_name, $field);
-            }
-        }
+        $idxname_format = $db->getOption('idxname_format');
+        $db->setOption('idxname_format', '%s');
 
         if (!empty($changes['remove']) && is_array($changes['remove'])) {
+            $result = $this->_dropConflictingIndices($name, array_keys($changes['remove']));
+            if (PEAR::isError($result)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $result;
+            }
+            $result = $this->_dropConflictingConstraints($name, array_keys($changes['remove']));
+            if (PEAR::isError($result)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $result;
+            }
+
+            $query = '';
             foreach ($changes['remove'] as $field_name => $field) {
                 if ($query) {
@@ -337,14 +431,234 @@
                 }
                 $field_name = $db->quoteIdentifier($field_name, true);
-                $query.= 'DROP COLUMN ' . $field_name;
-            }
-        }
-
-        if (!$query) {
-            return MDB2_OK;
-        }
-
-        $name = $db->quoteIdentifier($name, true);
-        return $db->exec("ALTER TABLE $name $query");
+                $query.= 'COLUMN ' . $field_name;
+            }
+
+            $result = $db->exec("ALTER TABLE $name_quoted DROP $query");
+            if (PEAR::isError($result)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $result;
+            }
+        }
+
+        if (!empty($changes['rename']) && is_array($changes['rename'])) {
+            foreach ($changes['rename'] as $field_name => $field) {
+                $field_name = $db->quoteIdentifier($field_name, true);
+                $result = $db->exec("sp_rename '$name_quoted.$field_name', '".$field['name']."', 'COLUMN'");
+                if (PEAR::isError($result)) {
+                    $db->setOption('idxname_format', $idxname_format);
+                    return $result;
+                }
+            }
+        }
+
+        if (!empty($changes['add']) && is_array($changes['add'])) {
+            $query = '';
+            foreach ($changes['add'] as $field_name => $field) {
+                if ($query) {
+                    $query.= ', ';
+                } else {
+                    $query.= 'ADD ';
+                }
+                $query.= $db->getDeclaration($field['type'], $field_name, $field);
+            }
+
+            $result = $db->exec("ALTER TABLE $name_quoted $query");
+            if (PEAR::isError($result)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $result;
+            }
+        }
+
+        $dropped_indices     = array();
+        $dropped_constraints = array();
+
+        if (!empty($changes['change']) && is_array($changes['change'])) {
+            $dropped = $this->_dropConflictingIndices($name, array_keys($changes['change']));
+            if (PEAR::isError($dropped)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $dropped;
+            }
+            $dropped_indices = array_merge($dropped_indices, $dropped);
+            $dropped = $this->_dropConflictingConstraints($name, array_keys($changes['change']));
+            if (PEAR::isError($dropped)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $dropped;
+            }
+            $dropped_constraints = array_merge($dropped_constraints, $dropped);
+
+            foreach ($changes['change'] as $field_name => $field) {
+                //MSSQL doesn't allow multiple ALTER COLUMNs in one query
+                $query = 'ALTER COLUMN ';
+
+                //MSSQL doesn't allow changing the DEFAULT value of a field in altering mode
+                if (array_key_exists('default', $field['definition'])) {
+                    unset($field['definition']['default']);
+                }
+
+                $query .= $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']);
+                $result = $db->exec("ALTER TABLE $name_quoted $query");
+                if (PEAR::isError($result)) {
+                    $db->setOption('idxname_format', $idxname_format);
+                    return $result;
+                }
+            }
+        }
+
+        // restore the dropped conflicting indices and constraints
+        foreach ($dropped_indices as $index_name => $index) {
+            $result = $this->createIndex($name, $index_name, $index);
+            if (PEAR::isError($result)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $result;
+            }
+        }
+        foreach ($dropped_constraints as $constraint_name => $constraint) {
+            $result = $this->createConstraint($name, $constraint_name, $constraint);
+            if (PEAR::isError($result)) {
+                $db->setOption('idxname_format', $idxname_format);
+                return $result;
+            }
+        }
+
+        $db->setOption('idxname_format', $idxname_format);
+
+        if (!empty($changes['name'])) {
+            $new_name = $db->quoteIdentifier($changes['name'], true);
+            $result = $db->exec("sp_rename '$name_quoted', '$new_name'");
+            if (PEAR::isError($result)) {
+                return $result;
+            }
+        }
+
+        return MDB2_OK;
+    }
+
+    // }}}
+    // {{{ _dropConflictingIndices()
+
+    /**
+     * Drop the indices that prevent a successful ALTER TABLE action
+     *
+     * @param string $table  table name
+     * @param array  $fields array of names of the fields affected by the change
+     *
+     * @return array dropped indices definitions
+     */
+    function _dropConflictingIndices($table, $fields)
+    {
+        $db =& $this->getDBInstance();
+        if (PEAR::isError($db)) {
+            return $db;
+        }
+
+        $dropped = array();
+        $index_names = $this->listTableIndexes($table);
+        if (PEAR::isError($index_names)) {
+            return $index_names;
+        }
+        $db->loadModule('Reverse');
+        $indexes = array();
+        foreach ($index_names as $index_name) {
+        	$idx_def = $db->reverse->getTableIndexDefinition($table, $index_name);
+            if (!PEAR::isError($idx_def)) {
+                $indexes[$index_name] = $idx_def;
+            }
+        }
+        foreach ($fields as $field_name) {
+            foreach ($indexes as $index_name => $index) {
+                if (!isset($dropped[$index_name]) && array_key_exists($field_name, $index['fields'])) {
+                    $dropped[$index_name] = $index;
+                    $result = $this->dropIndex($table, $index_name);
+                    if (PEAR::isError($result)) {
+                        return $result;
+                    }
+                }
+            }
+        }
+
+        return $dropped;
+    }
+
+    // }}}
+    // {{{ _dropConflictingConstraints()
+
+    /**
+     * Drop the constraints that prevent a successful ALTER TABLE action
+     *
+     * @param string $table  table name
+     * @param array  $fields array of names of the fields affected by the change
+     *
+     * @return array dropped constraints definitions
+     */
+    function _dropConflictingConstraints($table, $fields)
+    {
+        $db =& $this->getDBInstance();
+        if (PEAR::isError($db)) {
+            return $db;
+        }
+
+        $dropped = array();
+        $constraint_names = $this->listTableConstraints($table);
+        if (PEAR::isError($constraint_names)) {
+            return $constraint_names;
+        }
+        $db->loadModule('Reverse');
+        $constraints = array();
+        foreach ($constraint_names as $constraint_name) {
+        	$cons_def = $db->reverse->getTableConstraintDefinition($table, $constraint_name);
+            if (!PEAR::isError($cons_def)) {
+                $constraints[$constraint_name] = $cons_def;
+            }
+        }
+        foreach ($fields as $field_name) {
+            foreach ($constraints as $constraint_name => $constraint) {
+                if (!isset($dropped[$constraint_name]) && array_key_exists($field_name, $constraint['fields'])) {
+                    $dropped[$constraint_name] = $constraint;
+                    $result = $this->dropConstraint($table, $constraint_name);
+                    if (PEAR::isError($result)) {
+                        return $result;
+                    }
+                }
+            }
+            // also drop implicit DEFAULT constraints
+            $default = $this->_getTableFieldDefaultConstraint($table, $field_name);
+            if (!PEAR::isError($default) && !empty($default)) {
+                $result = $this->dropConstraint($table, $default);
+                if (PEAR::isError($result)) {
+                    return $result;
+                }
+            }
+        }
+
+        return $dropped;
+    }
+
+    // }}}
+    // {{{ _getTableFieldDefaultConstraint()
+
+    /**
+     * Get the default constraint for a table field
+     *
+     * @param string $table name of table that should be used in method
+     * @param string $field name of field that should be used in method
+     *
+     * @return mixed name of default constraint on success, a MDB2 error on failure
+     * @access private
+     */
+    function _getTableFieldDefaultConstraint($table, $field)
+    {
+        $db =& $this->getDBInstance();
+        if (PEAR::isError($db)) {
+            return $db;
+        }
+
+        $table = $db->quoteIdentifier($table, true);
+        $field = $db->quote($field, 'text');
+        $query = "SELECT OBJECT_NAME(syscolumns.cdefault)
+                    FROM syscolumns
+                   WHERE syscolumns.id = object_id('$table')
+                     AND syscolumns.name = $field
+                     AND syscolumns.cdefault <> 0";
+        return $db->queryOne($query);
     }
 
@@ -401,5 +715,5 @@
             return $db;
         }
-        
+
         $table = $db->quoteIdentifier($table, true);
         $columns = $db->queryCol("SELECT c.name
@@ -682,5 +996,5 @@
         }
         $table = $db->quoteIdentifier($table, true);
-        
+
         $query = "SELECT c.constraint_name
                     FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c
@@ -712,6 +1026,6 @@
      * create sequence
      *
-     * @param string $seq_name  name of the sequence to be created
-     * @param string $start     start value of the sequence; default is 1
+     * @param string $seq_name name of the sequence to be created
+     * @param string $start    start value of the sequence; default is 1
      *
      * @return mixed MDB2_OK on success, a MDB2 error on failure
