alecpl
2008-05-02 d1403fd7268ccf96ab6e7d04506ea1b1802c7eb2
program/lib/MDB2/Driver/Manager/mssql.php
@@ -2,7 +2,7 @@
// +----------------------------------------------------------------------+
// | 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.                                                 |
// +----------------------------------------------------------------------+
@@ -44,7 +44,7 @@
// |          Lorenzo Alberton <l.alberton@quipo.it>                      |
// +----------------------------------------------------------------------+
//
// $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 $
//
require_once 'MDB2/Driver/Manager/Common.php';
@@ -90,6 +90,39 @@
            $query .= ' COLLATE ' . $options['collation'];
        }
        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;
    }
    // }}}
@@ -200,6 +233,64 @@
    }
    // }}}
    // {{{ 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);
    }
    // }}}
    // {{{ alterTable()
    /**
@@ -298,16 +389,16 @@
        if (PEAR::isError($db)) {
            return $db;
        }
        $name_quoted = $db->quoteIdentifier($name, true);
        foreach ($changes as $change_name => $change) {
            switch ($change_name) {
            case 'add':
                break;
            case 'remove':
                break;
            case 'name':
            case 'rename':
            case 'add':
            case 'change':
            case 'name':
                break;
            default:
                return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null,
                    'change type "'.$change_name.'" not yet supported', __FUNCTION__);
@@ -318,34 +409,257 @@
            return MDB2_OK;
        }
        $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) {
                    $query.= ', ';
                }
                $field_name = $db->quoteIdentifier($field_name, true);
                $query.= 'DROP COLUMN ' . $field_name;
                $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 (!$query) {
        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;
        }
        $name = $db->quoteIdentifier($name, true);
        return $db->exec("ALTER TABLE $name $query");
    // }}}
    // {{{ _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);
    }
    // }}}