| | |
| | | // +----------------------------------------------------------------------+
|
| | | // | 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. |
|
| | | // +----------------------------------------------------------------------+
|
| | |
| | | // | 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';
|
| | |
| | | $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; |
| | | }
|
| | |
|
| | | // }}}
|
| | |
| | | }
|
| | |
|
| | | // }}}
|
| | | // {{{ 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()
|
| | |
|
| | | /**
|
| | |
| | | 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__);
|
| | |
| | | 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); |
| | | }
|
| | |
|
| | | // }}}
|