Add ability to add auto-increment to columns
authorMichael M Slusarz <slusarz@curecanti.org>
Mon, 24 May 2010 23:50:50 +0000 (17:50 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Tue, 25 May 2010 03:45:48 +0000 (21:45 -0600)
Only tested on PostgreSQL

framework/Db/lib/Horde/Db/Adapter/Base/ColumnDefinition.php
framework/Db/lib/Horde/Db/Adapter/Base/Schema.php
framework/Db/lib/Horde/Db/Adapter/Base/TableDefinition.php
framework/Db/lib/Horde/Db/Adapter/Mssql/Schema.php
framework/Db/lib/Horde/Db/Adapter/Mysql/Schema.php
framework/Db/lib/Horde/Db/Adapter/Oracle/Schema.php
framework/Db/lib/Horde/Db/Adapter/Postgresql/Schema.php
framework/Db/lib/Horde/Db/Adapter/Sqlite/Schema.php
framework/Db/package.xml

index 76c68ce..69c405e 100644 (file)
  */
 class Horde_Db_Adapter_Base_ColumnDefinition
 {
-    protected $_base      = null;
-    protected $_name      = null;
-    protected $_type      = null;
-    protected $_limit     = null;
-    protected $_precision = null;
-    protected $_scale     = null;
-    protected $_unsigned  = null;
-    protected $_default   = null;
-    protected $_null      = null;
+    protected $_base;
+    protected $_name;
+    protected $_type;
+    protected $_limit;
+    protected $_precision;
+    protected $_scale;
+    protected $_unsigned;
+    protected $_default;
+    protected $_null;
+    protected $_autoincrement;
 
     /**
      * Construct
      */
     public function __construct($base, $name, $type, $limit = null,
         $precision = null, $scale = null, $unsigned = null,
-        $default = null, $null = null)
+        $default = null, $null = null, $autoincrement = null)
     {
         // protected
         $this->_base      = $base;
 
         // public
-        $this->_name      = $name;
-        $this->_type      = $type;
-        $this->_limit     = $limit;
-        $this->_precision = $precision;
-        $this->_scale     = $scale;
-        $this->_unsigned  = $unsigned;
-        $this->_default   = $default;
-        $this->_null      = $null;
+        $this->_name          = $name;
+        $this->_type          = $type;
+        $this->_limit         = $limit;
+        $this->_precision     = $precision;
+        $this->_scale         = $scale;
+        $this->_unsigned      = $unsigned;
+        $this->_default       = $default;
+        $this->_null          = $null;
+        $this->_autoincrement = $autoincrement;
     }
 
 
@@ -159,6 +161,14 @@ class Horde_Db_Adapter_Base_ColumnDefinition
     }
 
     /**
+     * @return  boolean
+     */
+    public function isAutoIncrement()
+    {
+        return $this->_autoincrement;
+    }
+
+    /**
      * @param   string
      */
     public function setName($name)
@@ -222,6 +232,14 @@ class Horde_Db_Adapter_Base_ColumnDefinition
         $this->_null = $null;
     }
 
+    /**
+     * @param  boolean
+     */
+    public function setAutoIncrement($autoincrement)
+    {
+        $this->_autoincrement = $autoincrement;
+    }
+
 
     /*##########################################################################
     # Schema Statements
index 2957c6a..db30667 100644 (file)
@@ -706,14 +706,18 @@ abstract class Horde_Db_Adapter_Base_Schema
      */
     public function addColumnOptions($sql, $options)
     {
+        /* 'autoincrement' is not handled here - it varies too much between
+         * DBs. Do autoincrement specific handling in the driver. */
         if (isset($options['null']) && $options['null'] === false) {
             $sql .= ' NOT NULL';
         }
+
         if (isset($options['default'])) {
             $default = $options['default'];
             $column  = isset($options['column'])  ? $options['column']  : null;
             $sql .= ' DEFAULT '.$this->quote($default, $column);
         }
+
         return $sql;
     }
 
index c8b4d42..11d7f75 100644 (file)
@@ -92,6 +92,14 @@ class Horde_Db_Adapter_Base_TableDefinition implements ArrayAccess, IteratorAggr
      * * <tt>:null</tt>:
      *   Allows or disallows +NULL+ values in the column.  This option could
      *   have been named <tt>:null_allowed</tt>.
+     * * <tt>:precision</tt>
+     *   TODO
+     * * <tt>:scale</tt>
+     *   TODO
+     * * <tt>:unsigned</tt>
+     *   TODO
+     * * <tt>:autoincrement</tt>
+     *   TODO
      *
      * This method returns <tt>self</tt>.
      *
@@ -123,6 +131,7 @@ class Horde_Db_Adapter_Base_TableDefinition implements ArrayAccess, IteratorAggr
         $column->setUnsigned(isset($options['unsigned'])   ? $options['unsigned']  : null);
         $column->setDefault(isset($options['default'])     ? $options['default']   : null);
         $column->setNull(isset($options['null'])           ? $options['null']      : null);
+        $column->setAutoIncrement(isset($options['autoincrement']) ? $options['autoincrement'] : null);
 
         $this[$name] ? $this[$name] = $column : $this->_columns[] = $column;
         return $this;
index a999e5b..611e761 100644 (file)
@@ -49,7 +49,7 @@ class Horde_Db_Adapter_Mssql_Schema extends Horde_Db_Adapter_Base_Schema
     {
         return array(
             /* TODO, just put in nchar for unicode strings */
-            'primaryKey' => 'int(11) DEFAULT NULL auto_increment PRIMARY KEY',
+            'primaryKey' => 'int(11) DEFAULT NULL IDENTITY PRIMARY KEY',
             'string'     => array('name' => 'nchar',  'limit' => 255),
             'text'       => array('name' => 'text',     'limit' => null),
             'integer'    => array('name' => 'int',      'limit' => 11),
@@ -129,4 +129,40 @@ class Horde_Db_Adapter_Mssql_Schema extends Horde_Db_Adapter_Base_Schema
         return $this->selectOne('SELECT @@IDENTITY');
     }
 
+    /**
+     * Changes the column of a table.
+     */
+    public function changeColumn($tableName, $columnName, $type,
+                                 $options = array())
+    {
+        parent::changeColumn($tableName, $columnName, $type, $options);
+
+        if (!empty($options['autoincrement'])) {
+            /* Can't alter column in table - need to create, remove old
+             * column, and rename:
+             * http://www.eggheadcafe.com/software/aspnet/30132263/alter-table-add-identity.aspx */
+            // TODO: Better temp name?
+            $this->addColumn($tableName, $columnName . '_temp', $type, $options);
+            $this->removeColumn($tableName, $columnName);
+            $this->renameColumn($tableName, $columnName . '_temp', $columnName);
+        }
+    }
+
+    /**
+     * Add additional column options.
+     *
+     * @param   string  $sql
+     * @param   array   $options
+     * @return  string
+     */
+    public function addColumnOptions($sql, $options)
+    {
+        $sql = parent::addColumnOptions($sql, $options);
+        if (!empty($options['autoincrement'])) {
+            $sql .= ' IDENTITY';
+        }
+        return $sql;
+    }
+
+
 }
index ddbf4dd..24b3263 100644 (file)
@@ -414,7 +414,7 @@ class Horde_Db_Adapter_Mysql_Schema extends Horde_Db_Adapter_Base_Schema
     }
 
     /**
-     * Add AFTER option
+     * Add additional column options.
      *
      * @param   string  $sql
      * @param   array   $options
@@ -426,6 +426,9 @@ class Horde_Db_Adapter_Mysql_Schema extends Horde_Db_Adapter_Base_Schema
         if (isset($options['after'])) {
             $sql .= " AFTER ".$this->quoteColumnName($options['after']);
         }
+        if (!empty($options['autoincrement'])) {
+            $sql .= ' AUTO_INCREMENT';
+        }
         return $sql;
     }
 
index 321dac8..f269d0b 100644 (file)
@@ -35,4 +35,57 @@ class Horde_Db_Adapter_Oracle_Schema extends Horde_Db_Adapter_Base_Schema
         return '"' . str_replace('"', '""', $name) . '"';
     }
 
+    /**
+     * Adds a new column to the named table.
+     * See TableDefinition#column for details of the options you can use.
+     *
+     * @throws Horde_Db_Exception
+     */
+    public function addColumn($tableName, $columnName, $type,
+                              $options = array())
+    {
+        parent::addColumn($tableName, $columnName, $type, $options);
+
+        if (!empty($options['autoincrement'])) {
+            $this->_autoSequenceColumn($tableName, $columnName);
+        }
+    }
+
+    /**
+     * Changes the column of a table.
+     *
+     * @throws Horde_Db_Exception
+     */
+    public function changeColumn($tableName, $columnName, $type,
+                                 $options = array())
+    {
+        parent::changeColumn($tableName, $columnName, $type, $options);
+
+        if (!empty($options['autoincrement'])) {
+            $this->_autoSequenceColumn($tableName, $columnName);
+        }
+    }
+
+    /**
+     * Add auto-sequencing to a column.
+     *
+     * @throws Horde_Db_Exception
+     */
+    protected function _autoSequenceColumn($tableName, $columnName)
+    {
+        $seq_name = $tableName . '_' . $columnName . '_seq';
+        $trigger_name = $tableName . '_' . $columnName . '_trigger';
+
+        $this->beginDbTransaction();
+        $this->executeWrite('CREATE SEQUENCE ' . $seq_name);
+        $this->executeWrite(
+            'CREATE TRIGGER ' . $trigger_name . ' ' .
+            'BEFORE INSERT ON ' . $this->quoteTableName($tableName) . ' ' .
+            'FOR EACH ROW '
+            'BEGIN '
+            'SELECT ' . $seq_name . '.nextval INTO :new.' . $columnName . ' FROM dual; END'
+        );
+        $this->commitDbTransaction();
+    }
+
 }
index c49f97d..991fe75 100644 (file)
@@ -437,23 +437,45 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
      * Adds a new column to the named table.
      * See TableDefinition#column for details of the options you can use.
      */
-    public function addColumn($tableName, $columnName, $type, $options = array())
+    public function addColumn($tableName, $columnName, $type,
+                              $options = array())
     {
         $this->_clearTableCache($tableName);
 
+        $autoincrement = isset($options['autoincrement']) ? $options['autoincrement'] : null;
         $limit     = isset($options['limit'])     ? $options['limit']     : null;
         $precision = isset($options['precision']) ? $options['precision'] : null;
         $scale     = isset($options['scale'])     ? $options['scale']     : null;
 
+        $sqltype = $this->typeToSql($type, $limit, $precision, $scale);
+
+        /* Convert to SERIAL type if needed. */
+        if ($autoincrement) {
+            switch ($sqltype) {
+            case 'bigint':
+                $sqltype = 'BIGSERIAL';
+                break;
+
+            case 'integer':
+            default:
+                $sqltype = 'SERIAL';
+                break;
+            }
+        }
+
         // Add the column.
-        $this->execute('ALTER TABLE '.$this->quoteTableName($tableName).' ADD COLUMN '.$this->quoteColumnName($columnName).' '.$this->typeToSql($type, $limit, $precision, $scale));
+        $this->execute('ALTER TABLE ' . $this->quoteTableName($tableName) . ' ADD COLUMN ' . $this->quoteColumnName($columnName) . ' ' . $sqltype);
 
         $default = isset($options['default']) ? $options['default'] : null;
         $notnull = isset($options['null']) && $options['null'] === false;
-        if (array_key_exists('default', $options))
+
+        if (array_key_exists('default', $options)) {
             $this->changeColumnDefault($tableName, $columnName, $default);
-        if ($notnull)
+        }
+
+        if ($notnull) {
             $this->changeColumnNull($tableName, $columnName, false, $default);
+        }
     }
 
     /**
@@ -463,6 +485,7 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
     {
         $this->_clearTableCache($tableName);
 
+        $autoincrement = isset($options['autoincrement']) ? $options['autoincrement'] : null;
         $limit     = isset($options['limit'])     ? $options['limit']     : null;
         $precision = isset($options['precision']) ? $options['precision'] : null;
         $scale     = isset($options['scale'])     ? $options['scale']     : null;
@@ -511,10 +534,18 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
         }
 
         $default = isset($options['default']) ? $options['default'] : null;
-        if (array_key_exists('default', $options))
+
+        if ($autoincrement) {
+            $seq_name = $this->quoteSequenceName($this->defaultSequenceName($tableName, $columnName));
+            $this->execute('CREATE SEQUENCE ' . $seq_name);
+            $this->changeColumnDefault($tableName, $columnName, 'NEXTVAL(' . $seq_name . ')');
+        } elseif (array_key_exists('default', $options)) {
             $this->changeColumnDefault($tableName, $columnName, $default);
-        if (array_key_exists('null', $options))
+        }
+
+        if (array_key_exists('null', $options)) {
             $this->changeColumnNull($tableName, $columnName, $options['null'], $default);
+        }
     }
 
     /**
@@ -555,27 +586,34 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
 
     /**
      * Maps logical Rails types to PostgreSQL-specific data types.
+     *
+     * @throws Horde_Db_Exception
      */
-    public function typeToSql($type, $limit = null, $precision = null, $scale = null, $unsigned = null)
+    public function typeToSql($type, $limit = null, $precision = null,
+                              $scale = null, $unsigned = null)
     {
-        if ($type != 'integer') return parent::typeToSql($type, $limit, $precision, $scale);
+        if ($type != 'integer') {
+            return parent::typeToSql($type, $limit, $precision, $scale);
+        }
 
         switch ($limit) {
         case 1:
         case 2:
             return 'smallint';
+
         case 3:
         case 4:
         case null:
             return 'integer';
+
         case 5:
         case 6:
         case 7:
         case 8:
             return 'bigint';
-        default:
-            throw new Horde_Db_Exception("No integer type has byte size $limit. Use a numeric with precision 0 instead.");
         }
+
+        throw new Horde_Db_Exception("No integer type has byte size $limit. Use a numeric with precision 0 instead.");
     }
 
     /**
index 6ff7efa..76a1d79 100644 (file)
@@ -207,6 +207,8 @@ class Horde_Db_Adapter_Sqlite_Schema extends Horde_Db_Adapter_Base_Schema
      */
     public function addColumn($tableName, $columnName, $type, $options=array())
     {
+        /* Ignore ':autoincrement' - it is handled automatically my SQLite
+         * for any 'INTEGER PRIMARY KEY' column. */
         if ($this->transactionStarted()) {
             throw new Horde_Db_Exception('Cannot add columns to a SQLite database while inside a transaction');
         }
index 37faba3..ee6ac00 100644 (file)
@@ -30,7 +30,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <api>beta</api>
  </stability>
  <license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
- <notes>* Add ability to define a write DB for use with split-DB installations.
+ <notes>* Add support for adding autoincrement to a column.
+ * Add ability to define a write DB for use with split-DB installations.
  * Initial release
  </notes>
  <contents>