cms/drupal/includes/database/pgsql/schema.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Database schema code for PostgreSQL database servers.
       
     6  */
       
     7 
       
     8 /**
       
     9  * @ingroup schemaapi
       
    10  * @{
       
    11  */
       
    12 
       
    13 class DatabaseSchema_pgsql extends DatabaseSchema {
       
    14 
       
    15   /**
       
    16    * A cache of information about blob columns and sequences of tables.
       
    17    *
       
    18    * This is collected by DatabaseConnection_pgsql->queryTableInformation(),
       
    19    * by introspecting the database.
       
    20    *
       
    21    * @see DatabaseConnection_pgsql->queryTableInformation()
       
    22    * @var array
       
    23    */
       
    24   protected $tableInformation = array();
       
    25 
       
    26   /**
       
    27    * Fetch the list of blobs and sequences used on a table.
       
    28    *
       
    29    * We introspect the database to collect the information required by insert
       
    30    * and update queries.
       
    31    *
       
    32    * @param $table_name
       
    33    *   The non-prefixed name of the table.
       
    34    * @return
       
    35    *   An object with two member variables:
       
    36    *     - 'blob_fields' that lists all the blob fields in the table.
       
    37    *     - 'sequences' that lists the sequences used in that table.
       
    38    */
       
    39   public function queryTableInformation($table) {
       
    40     // Generate a key to reference this table's information on.
       
    41     $key = $this->connection->prefixTables('{' . $table . '}');
       
    42     if (!strpos($key, '.')) {
       
    43       $key = 'public.' . $key;
       
    44     }
       
    45 
       
    46     if (!isset($this->tableInformation[$key])) {
       
    47       // Split the key into schema and table for querying.
       
    48       list($schema, $table_name) = explode('.', $key);
       
    49       $table_information = (object) array(
       
    50         'blob_fields' => array(),
       
    51         'sequences' => array(),
       
    52       );
       
    53       // Don't use {} around information_schema.columns table.
       
    54       $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(
       
    55         ':schema' => $schema,
       
    56         ':table' => $table_name,
       
    57         ':default' => '%nextval%',
       
    58       ));
       
    59       foreach ($result as $column) {
       
    60         if ($column->data_type == 'bytea') {
       
    61           $table_information->blob_fields[$column->column_name] = TRUE;
       
    62         }
       
    63         elseif (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) {
       
    64           // We must know of any sequences in the table structure to help us
       
    65           // return the last insert id. If there is more than 1 sequences the
       
    66           // first one (index 0 of the sequences array) will be used.
       
    67           $table_information->sequences[] = $matches[1];
       
    68           $table_information->serial_fields[] = $column->column_name;
       
    69         }
       
    70       }
       
    71       $this->tableInformation[$key] = $table_information;
       
    72     }
       
    73     return $this->tableInformation[$key];
       
    74   }
       
    75 
       
    76   /**
       
    77    * Fetch the list of CHECK constraints used on a field.
       
    78    *
       
    79    * We introspect the database to collect the information required by field
       
    80    * alteration.
       
    81    *
       
    82    * @param $table
       
    83    *   The non-prefixed name of the table.
       
    84    * @param $field
       
    85    *   The name of the field.
       
    86    * @return
       
    87    *   An array of all the checks for the field.
       
    88    */
       
    89   public function queryFieldInformation($table, $field) {
       
    90     $prefixInfo = $this->getPrefixInfo($table, TRUE);
       
    91 
       
    92     // Split the key into schema and table for querying.
       
    93     $schema = $prefixInfo['schema'];
       
    94     $table_name = $prefixInfo['table'];
       
    95 
       
    96     $field_information = (object) array(
       
    97         'checks' => array(),
       
    98     );
       
    99     $checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array(
       
   100       ':schema' => $schema,
       
   101       ':table' => $table_name,
       
   102       ':column' => $field,
       
   103     ));
       
   104     $field_information = $checks->fetchCol();
       
   105 
       
   106     return $field_information;
       
   107   }
       
   108 
       
   109   /**
       
   110    * Generate SQL to create a new table from a Drupal schema definition.
       
   111    *
       
   112    * @param $name
       
   113    *   The name of the table to create.
       
   114    * @param $table
       
   115    *   A Schema API table definition array.
       
   116    * @return
       
   117    *   An array of SQL statements to create the table.
       
   118    */
       
   119   protected function createTableSql($name, $table) {
       
   120     $sql_fields = array();
       
   121     foreach ($table['fields'] as $field_name => $field) {
       
   122       $sql_fields[] = $this->createFieldSql($field_name, $this->processField($field));
       
   123     }
       
   124 
       
   125     $sql_keys = array();
       
   126     if (isset($table['primary key']) && is_array($table['primary key'])) {
       
   127       $sql_keys[] = 'PRIMARY KEY (' . implode(', ', $table['primary key']) . ')';
       
   128     }
       
   129     if (isset($table['unique keys']) && is_array($table['unique keys'])) {
       
   130       foreach ($table['unique keys'] as $key_name => $key) {
       
   131         $sql_keys[] = 'CONSTRAINT ' . $this->prefixNonTable($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')';
       
   132       }
       
   133     }
       
   134 
       
   135     $sql = "CREATE TABLE {" . $name . "} (\n\t";
       
   136     $sql .= implode(",\n\t", $sql_fields);
       
   137     if (count($sql_keys) > 0) {
       
   138       $sql .= ",\n\t";
       
   139     }
       
   140     $sql .= implode(",\n\t", $sql_keys);
       
   141     $sql .= "\n)";
       
   142     $statements[] = $sql;
       
   143 
       
   144     if (isset($table['indexes']) && is_array($table['indexes'])) {
       
   145       foreach ($table['indexes'] as $key_name => $key) {
       
   146         $statements[] = $this->_createIndexSql($name, $key_name, $key);
       
   147       }
       
   148     }
       
   149 
       
   150     // Add table comment.
       
   151     if (!empty($table['description'])) {
       
   152       $statements[] = 'COMMENT ON TABLE {' . $name . '} IS ' . $this->prepareComment($table['description']);
       
   153     }
       
   154 
       
   155     // Add column comments.
       
   156     foreach ($table['fields'] as $field_name => $field) {
       
   157       if (!empty($field['description'])) {
       
   158         $statements[] = 'COMMENT ON COLUMN {' . $name . '}.' . $field_name . ' IS ' . $this->prepareComment($field['description']);
       
   159       }
       
   160     }
       
   161 
       
   162     return $statements;
       
   163   }
       
   164 
       
   165   /**
       
   166    * Create an SQL string for a field to be used in table creation or
       
   167    * alteration.
       
   168    *
       
   169    * Before passing a field out of a schema definition into this
       
   170    * function it has to be processed by _db_process_field().
       
   171    *
       
   172    * @param $name
       
   173    *    Name of the field.
       
   174    * @param $spec
       
   175    *    The field specification, as per the schema data structure format.
       
   176    */
       
   177   protected function createFieldSql($name, $spec) {
       
   178     $sql = $name . ' ' . $spec['pgsql_type'];
       
   179 
       
   180     if (isset($spec['type']) && $spec['type'] == 'serial') {
       
   181       unset($spec['not null']);
       
   182     }
       
   183 
       
   184     if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
       
   185       $sql .= '(' . $spec['length'] . ')';
       
   186     }
       
   187     elseif (isset($spec['precision']) && isset($spec['scale'])) {
       
   188       $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
       
   189     }
       
   190 
       
   191     if (!empty($spec['unsigned'])) {
       
   192       $sql .= " CHECK ($name >= 0)";
       
   193     }
       
   194 
       
   195     if (isset($spec['not null'])) {
       
   196       if ($spec['not null']) {
       
   197         $sql .= ' NOT NULL';
       
   198       }
       
   199       else {
       
   200         $sql .= ' NULL';
       
   201       }
       
   202     }
       
   203     if (isset($spec['default'])) {
       
   204       $default = is_string($spec['default']) ? "'" . $spec['default'] . "'" : $spec['default'];
       
   205       $sql .= " default $default";
       
   206     }
       
   207 
       
   208     return $sql;
       
   209   }
       
   210 
       
   211   /**
       
   212    * Set database-engine specific properties for a field.
       
   213    *
       
   214    * @param $field
       
   215    *   A field description array, as specified in the schema documentation.
       
   216    */
       
   217   protected function processField($field) {
       
   218     if (!isset($field['size'])) {
       
   219       $field['size'] = 'normal';
       
   220     }
       
   221 
       
   222     // Set the correct database-engine specific datatype.
       
   223     // In case one is already provided, force it to lowercase.
       
   224     if (isset($field['pgsql_type'])) {
       
   225       $field['pgsql_type'] = drupal_strtolower($field['pgsql_type']);
       
   226     }
       
   227     else {
       
   228       $map = $this->getFieldTypeMap();
       
   229       $field['pgsql_type'] = $map[$field['type'] . ':' . $field['size']];
       
   230     }
       
   231 
       
   232     if (!empty($field['unsigned'])) {
       
   233       // Unsigned datatypes are not supported in PostgreSQL 8.3. In MySQL,
       
   234       // they are used to ensure a positive number is inserted and it also
       
   235       // doubles the maximum integer size that can be stored in a field.
       
   236       // The PostgreSQL schema in Drupal creates a check constraint
       
   237       // to ensure that a value inserted is >= 0. To provide the extra
       
   238       // integer capacity, here, we bump up the column field size.
       
   239       if (!isset($map)) {
       
   240         $map = $this->getFieldTypeMap();
       
   241       }
       
   242       switch ($field['pgsql_type']) {
       
   243         case 'smallint':
       
   244           $field['pgsql_type'] = $map['int:medium'];
       
   245           break;
       
   246         case 'int' :
       
   247           $field['pgsql_type'] = $map['int:big'];
       
   248           break;
       
   249       }
       
   250     }
       
   251     if (isset($field['type']) && $field['type'] == 'serial') {
       
   252       unset($field['not null']);
       
   253     }
       
   254     return $field;
       
   255   }
       
   256 
       
   257   /**
       
   258    * This maps a generic data type in combination with its data size
       
   259    * to the engine-specific data type.
       
   260    */
       
   261   function getFieldTypeMap() {
       
   262     // Put :normal last so it gets preserved by array_flip. This makes
       
   263     // it much easier for modules (such as schema.module) to map
       
   264     // database types back into schema types.
       
   265     // $map does not use drupal_static as its value never changes.
       
   266     static $map = array(
       
   267       'varchar:normal' => 'varchar',
       
   268       'char:normal' => 'character',
       
   269 
       
   270       'text:tiny' => 'text',
       
   271       'text:small' => 'text',
       
   272       'text:medium' => 'text',
       
   273       'text:big' => 'text',
       
   274       'text:normal' => 'text',
       
   275 
       
   276       'int:tiny' => 'smallint',
       
   277       'int:small' => 'smallint',
       
   278       'int:medium' => 'int',
       
   279       'int:big' => 'bigint',
       
   280       'int:normal' => 'int',
       
   281 
       
   282       'float:tiny' => 'real',
       
   283       'float:small' => 'real',
       
   284       'float:medium' => 'real',
       
   285       'float:big' => 'double precision',
       
   286       'float:normal' => 'real',
       
   287 
       
   288       'numeric:normal' => 'numeric',
       
   289 
       
   290       'blob:big' => 'bytea',
       
   291       'blob:normal' => 'bytea',
       
   292 
       
   293       'serial:tiny' => 'serial',
       
   294       'serial:small' => 'serial',
       
   295       'serial:medium' => 'serial',
       
   296       'serial:big' => 'bigserial',
       
   297       'serial:normal' => 'serial',
       
   298       );
       
   299     return $map;
       
   300   }
       
   301 
       
   302   protected function _createKeySql($fields) {
       
   303     $return = array();
       
   304     foreach ($fields as $field) {
       
   305       if (is_array($field)) {
       
   306         $return[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')';
       
   307       }
       
   308       else {
       
   309         $return[] = '"' . $field . '"';
       
   310       }
       
   311     }
       
   312     return implode(', ', $return);
       
   313   }
       
   314 
       
   315   function renameTable($table, $new_name) {
       
   316     if (!$this->tableExists($table)) {
       
   317       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
       
   318     }
       
   319     if ($this->tableExists($new_name)) {
       
   320       throw new DatabaseSchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
       
   321     }
       
   322 
       
   323     // Get the schema and tablename for the old table.
       
   324     $old_full_name = $this->connection->prefixTables('{' . $table . '}');
       
   325     list($old_schema, $old_table_name) = strpos($old_full_name, '.') ? explode('.', $old_full_name) : array('public', $old_full_name);
       
   326 
       
   327     // Index names and constraint names are global in PostgreSQL, so we need to
       
   328     // rename them when renaming the table.
       
   329     $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name));
       
   330     foreach ($indexes as $index) {
       
   331       if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) {
       
   332         $index_name = $matches[1];
       
   333         $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name);
       
   334       }
       
   335     }
       
   336 
       
   337     // Now rename the table.
       
   338     // Ensure the new table name does not include schema syntax.
       
   339     $prefixInfo = $this->getPrefixInfo($new_name);
       
   340     $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $prefixInfo['table']);
       
   341   }
       
   342 
       
   343   public function dropTable($table) {
       
   344     if (!$this->tableExists($table)) {
       
   345       return FALSE;
       
   346     }
       
   347 
       
   348     $this->connection->query('DROP TABLE {' . $table . '}');
       
   349     return TRUE;
       
   350   }
       
   351 
       
   352   public function addField($table, $field, $spec, $new_keys = array()) {
       
   353     if (!$this->tableExists($table)) {
       
   354       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
       
   355     }
       
   356     if ($this->fieldExists($table, $field)) {
       
   357       throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
       
   358     }
       
   359 
       
   360     $fixnull = FALSE;
       
   361     if (!empty($spec['not null']) && !isset($spec['default'])) {
       
   362       $fixnull = TRUE;
       
   363       $spec['not null'] = FALSE;
       
   364     }
       
   365     $query = 'ALTER TABLE {' . $table . '} ADD COLUMN ';
       
   366     $query .= $this->createFieldSql($field, $this->processField($spec));
       
   367     $this->connection->query($query);
       
   368     if (isset($spec['initial'])) {
       
   369       $this->connection->update($table)
       
   370         ->fields(array($field => $spec['initial']))
       
   371         ->execute();
       
   372     }
       
   373     if ($fixnull) {
       
   374       $this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL");
       
   375     }
       
   376     if (isset($new_keys)) {
       
   377       $this->_createKeys($table, $new_keys);
       
   378     }
       
   379     // Add column comment.
       
   380     if (!empty($spec['description'])) {
       
   381       $this->connection->query('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description']));
       
   382     }
       
   383   }
       
   384 
       
   385   public function dropField($table, $field) {
       
   386     if (!$this->fieldExists($table, $field)) {
       
   387       return FALSE;
       
   388     }
       
   389 
       
   390     $this->connection->query('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"');
       
   391     return TRUE;
       
   392   }
       
   393 
       
   394   public function fieldSetDefault($table, $field, $default) {
       
   395     if (!$this->fieldExists($table, $field)) {
       
   396       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
       
   397     }
       
   398 
       
   399     if (!isset($default)) {
       
   400       $default = 'NULL';
       
   401     }
       
   402     else {
       
   403       $default = is_string($default) ? "'$default'" : $default;
       
   404     }
       
   405 
       
   406     $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default);
       
   407   }
       
   408 
       
   409   public function fieldSetNoDefault($table, $field) {
       
   410     if (!$this->fieldExists($table, $field)) {
       
   411       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
       
   412     }
       
   413 
       
   414     $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
       
   415   }
       
   416 
       
   417   public function indexExists($table, $name) {
       
   418     // Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html
       
   419     $index_name = '{' . $table . '}_' . $name . '_idx';
       
   420     return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField();
       
   421   }
       
   422 
       
   423   /**
       
   424    * Helper function: check if a constraint (PK, FK, UK) exists.
       
   425    *
       
   426    * @param $table
       
   427    *   The name of the table.
       
   428    * @param $name
       
   429    *   The name of the constraint (typically 'pkey' or '[constraint]_key').
       
   430    */
       
   431   protected function constraintExists($table, $name) {
       
   432     $constraint_name = '{' . $table . '}_' . $name;
       
   433     return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField();
       
   434   }
       
   435 
       
   436   public function addPrimaryKey($table, $fields) {
       
   437     if (!$this->tableExists($table)) {
       
   438       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
       
   439     }
       
   440     if ($this->constraintExists($table, 'pkey')) {
       
   441       throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
       
   442     }
       
   443 
       
   444     $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')');
       
   445   }
       
   446 
       
   447   public function dropPrimaryKey($table) {
       
   448     if (!$this->constraintExists($table, 'pkey')) {
       
   449       return FALSE;
       
   450     }
       
   451 
       
   452     $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->prefixNonTable($table, 'pkey'));
       
   453     return TRUE;
       
   454   }
       
   455 
       
   456   function addUniqueKey($table, $name, $fields) {
       
   457     if (!$this->tableExists($table)) {
       
   458       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
       
   459     }
       
   460     if ($this->constraintExists($table, $name . '_key')) {
       
   461       throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
       
   462     }
       
   463 
       
   464     $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
       
   465   }
       
   466 
       
   467   public function dropUniqueKey($table, $name) {
       
   468     if (!$this->constraintExists($table, $name . '_key')) {
       
   469       return FALSE;
       
   470     }
       
   471 
       
   472     $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '"');
       
   473     return TRUE;
       
   474   }
       
   475 
       
   476   public function addIndex($table, $name, $fields) {
       
   477     if (!$this->tableExists($table)) {
       
   478       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
       
   479     }
       
   480     if ($this->indexExists($table, $name)) {
       
   481       throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
       
   482     }
       
   483 
       
   484     $this->connection->query($this->_createIndexSql($table, $name, $fields));
       
   485   }
       
   486 
       
   487   public function dropIndex($table, $name) {
       
   488     if (!$this->indexExists($table, $name)) {
       
   489       return FALSE;
       
   490     }
       
   491 
       
   492     $this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx'));
       
   493     return TRUE;
       
   494   }
       
   495 
       
   496   public function changeField($table, $field, $field_new, $spec, $new_keys = array()) {
       
   497     if (!$this->fieldExists($table, $field)) {
       
   498       throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
       
   499     }
       
   500     if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
       
   501       throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
       
   502     }
       
   503 
       
   504     $spec = $this->processField($spec);
       
   505 
       
   506     // We need to typecast the new column to best be able to transfer the data
       
   507     // Schema_pgsql::getFieldTypeMap() will return possibilities that are not
       
   508     // 'cast-able' such as 'serial' - so they need to be casted int instead.
       
   509     if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) {
       
   510       $typecast = 'int';
       
   511     }
       
   512     else {
       
   513       $typecast = $spec['pgsql_type'];
       
   514     }
       
   515 
       
   516     if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
       
   517       $typecast .= '(' . $spec['length'] . ')';
       
   518     }
       
   519     elseif (isset($spec['precision']) && isset($spec['scale'])) {
       
   520       $typecast .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
       
   521     }
       
   522 
       
   523     // Remove old check constraints.
       
   524     $field_info = $this->queryFieldInformation($table, $field);
       
   525 
       
   526     foreach ($field_info as $check) {
       
   527       $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $check . '"');
       
   528     }
       
   529 
       
   530     // Remove old default.
       
   531     $this->fieldSetNoDefault($table, $field);
       
   532 
       
   533     $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast);
       
   534 
       
   535     if (isset($spec['not null'])) {
       
   536       if ($spec['not null']) {
       
   537         $nullaction = 'SET NOT NULL';
       
   538       }
       
   539       else {
       
   540         $nullaction = 'DROP NOT NULL';
       
   541       }
       
   542       $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" ' . $nullaction);
       
   543     }
       
   544 
       
   545     if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) {
       
   546       // Type "serial" is known to PostgreSQL, but *only* during table creation,
       
   547       // not when altering. Because of that, the sequence needs to be created
       
   548       // and initialized by hand.
       
   549       $seq = "{" . $table . "}_" . $field_new . "_seq";
       
   550       $this->connection->query("CREATE SEQUENCE " . $seq);
       
   551       // Set sequence to maximal field value to not conflict with existing
       
   552       // entries.
       
   553       $this->connection->query("SELECT setval('" . $seq . "', MAX(\"" . $field . '")) FROM {' . $table . "}");
       
   554       $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" SET DEFAULT nextval(\'' . $seq . '\')');
       
   555     }
       
   556 
       
   557     // Rename the column if necessary.
       
   558     if ($field != $field_new) {
       
   559       $this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field_new . '"');
       
   560     }
       
   561 
       
   562     // Add unsigned check if necessary.
       
   563     if (!empty($spec['unsigned'])) {
       
   564       $this->connection->query('ALTER TABLE {' . $table . '} ADD CHECK ("' . $field_new . '" >= 0)');
       
   565     }
       
   566 
       
   567     // Add default if necessary.
       
   568     if (isset($spec['default'])) {
       
   569       $this->fieldSetDefault($table, $field_new, $spec['default']);
       
   570     }
       
   571 
       
   572     // Change description if necessary.
       
   573     if (!empty($spec['description'])) {
       
   574       $this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description']));
       
   575     }
       
   576 
       
   577     if (isset($new_keys)) {
       
   578       $this->_createKeys($table, $new_keys);
       
   579     }
       
   580   }
       
   581 
       
   582   protected function _createIndexSql($table, $name, $fields) {
       
   583     $query = 'CREATE INDEX "' . $this->prefixNonTable($table, $name, 'idx') . '" ON {' . $table . '} (';
       
   584     $query .= $this->_createKeySql($fields) . ')';
       
   585     return $query;
       
   586   }
       
   587 
       
   588   protected function _createKeys($table, $new_keys) {
       
   589     if (isset($new_keys['primary key'])) {
       
   590       $this->addPrimaryKey($table, $new_keys['primary key']);
       
   591     }
       
   592     if (isset($new_keys['unique keys'])) {
       
   593       foreach ($new_keys['unique keys'] as $name => $fields) {
       
   594         $this->addUniqueKey($table, $name, $fields);
       
   595       }
       
   596     }
       
   597     if (isset($new_keys['indexes'])) {
       
   598       foreach ($new_keys['indexes'] as $name => $fields) {
       
   599         $this->addIndex($table, $name, $fields);
       
   600       }
       
   601     }
       
   602   }
       
   603 
       
   604   /**
       
   605    * Retrieve a table or column comment.
       
   606    */
       
   607   public function getComment($table, $column = NULL) {
       
   608     $info = $this->getPrefixInfo($table);
       
   609     // Don't use {} around pg_class, pg_attribute tables.
       
   610     if (isset($column)) {
       
   611       return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField();
       
   612     }
       
   613     else {
       
   614       return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
       
   615     }
       
   616   }
       
   617 }