Add support for unsigned integers
authorChuck Hagenbuch <chuck@horde.org>
Mon, 11 Jan 2010 04:21:10 +0000 (23:21 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Mon, 11 Jan 2010 04:21:31 +0000 (23:21 -0500)
12 files changed:
framework/Db/lib/Horde/Db/Adapter/Base/Column.php
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/Mysql/Schema.php
framework/Db/lib/Horde/Db/Adapter/Postgresql/Schema.php
framework/Db/test/Horde/Db/Adapter/Mysql/ColumnDefinitionTest.php
framework/Db/test/Horde/Db/Adapter/Mysql/ColumnTest.php
framework/Db/test/Horde/Db/Adapter/Postgresql/ColumnDefinitionTest.php
framework/Db/test/Horde/Db/Adapter/Postgresql/ColumnTest.php
framework/Db/test/Horde/Db/Adapter/Sqlite/ColumnDefinitionTest.php
framework/Db/test/Horde/Db/Adapter/Sqlite/ColumnTest.php

index 433e308..6d32e40 100644 (file)
@@ -29,6 +29,7 @@ class Horde_Db_Adapter_Base_Column
     protected $_limit;
     protected $_precision;
     protected $_scale;
+    protected $_unsigned;
     protected $_default;
     protected $_sqlType;
     protected $_isText;
@@ -44,7 +45,7 @@ class Horde_Db_Adapter_Base_Column
      *
      * @param   string  $name     The column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
      * @param   string  $default  The type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
-     * @param   string  $sqlType  Only used to extract the column's length, if necessary. For example +60+ in <tt>company_name varchar(60)</tt>.
+     * @param   string  $sqlType  Used to extract the column's length and signed status, if necessary. For example +60+ in <tt>company_name varchar(60)</tt>, or +unsigned => true+ in <tt>int(10) UNSIGNED</tt>.
      * @param   boolean $null     Determines if this column allows +NULL+ values.
      */
     public function __construct($name, $default, $sqlType = null, $null = true)
@@ -56,6 +57,7 @@ class Horde_Db_Adapter_Base_Column
         $this->_limit     = $this->_extractLimit($sqlType);
         $this->_precision = $this->_extractPrecision($sqlType);
         $this->_scale     = $this->_extractScale($sqlType);
+        $this->_unsigned  = $this->_extractUnsigned($sqlType);
 
         $this->_type      = $this->_simplifiedType($sqlType);
         $this->_isText    = $this->_type == 'text'  || $this->_type == 'string';
@@ -174,6 +176,14 @@ class Horde_Db_Adapter_Base_Column
     /**
      * @return  boolean
      */
+    public function isUnsigned()
+    {
+        return $this->_unsigned;
+    }
+
+    /**
+     * @return  boolean
+     */
     public function isNull()
     {
         return $this->_null;
@@ -327,6 +337,15 @@ class Horde_Db_Adapter_Base_Column
     }
 
     /**
+     * @param   string  $sqlType
+     * @return  int
+     */
+    protected function _extractUnsigned($sqlType)
+    {
+        return (boolean)preg_match('/^int.*unsigned/i', $sqlType);
+    }
+
+    /**
      * @param   string  $fieldType
      * @return  string
      */
@@ -357,5 +376,4 @@ class Horde_Db_Adapter_Base_Column
                 return 'boolean';
         }
     }
-
 }
index c8f493d..1b05d84 100644 (file)
@@ -29,14 +29,16 @@ class Horde_Db_Adapter_Base_ColumnDefinition
     protected $_limit     = null;
     protected $_precision = null;
     protected $_scale     = null;
+    protected $_unsigned  = null;
     protected $_default   = null;
     protected $_null      = null;
 
     /**
      * Construct
      */
-    public function __construct($base, $name, $type, $limit=null,
-         $precision=null, $scale=null, $default=null, $null=null)
+    public function __construct($base, $name, $type, $limit = null,
+        $precision = null, $scale = null, $unsigned = null,
+        $default = null, $null = null)
     {
         // protected
         $this->_base      = $base;
@@ -47,10 +49,12 @@ class Horde_Db_Adapter_Base_ColumnDefinition
         $this->_limit     = $limit;
         $this->_precision = $precision;
         $this->_scale     = $scale;
+        $this->_unsigned  = $unsigned;
         $this->_default   = $default;
         $this->_null      = $null;
     }
 
+
     /*##########################################################################
     # Public
     ##########################################################################*/
@@ -60,15 +64,9 @@ class Horde_Db_Adapter_Base_ColumnDefinition
      */
     public function toSql()
     {
-        $sql = $this->_base->quoteColumnName($this->_name).' ';
-        try {
-            $sql .= $this->_base->typeToSql($this->_type,      $this->_limit,
-                                            $this->_precision, $this->_scale);
-        } catch (Exception $e) {
-            $sql .= $this->_type;
-        }
-        return $this->_addColumnOptions($sql, array('null'    => $this->_null,
-                                                    'default' => $this->_default));
+        $sql = $this->_base->quoteColumnName($this->_name) . ' ' . $this->getSqlType();
+        return $this->_addColumnOptions($sql, array('null'     => $this->_null,
+                                                    'default'  => $this->_default));
     }
 
     /**
@@ -114,7 +112,7 @@ class Horde_Db_Adapter_Base_ColumnDefinition
     public function getSqlType()
     {
         try {
-            return $this->_base->typeToSql($this->_type, $this->_limit, $this->_precision, $this->_scale);
+            return $this->_base->typeToSql($this->_type, $this->_limit, $this->_precision, $this->_scale, $this->_unsigned);
         } catch (Exception $e) {
             return $this->_type;
         }
@@ -147,6 +145,14 @@ class Horde_Db_Adapter_Base_ColumnDefinition
     /**
      * @return  boolean
      */
+    public function isUnsigned()
+    {
+        return $this->_unsigned;
+    }
+
+    /**
+     * @return  boolean
+     */
     public function isNull()
     {
         return $this->_null;
@@ -203,6 +209,14 @@ class Horde_Db_Adapter_Base_ColumnDefinition
     /**
      * @param  boolean
      */
+    public function setUnsigned($unsigned)
+    {
+        $this->_unsigned = $unsigned;
+    }
+
+    /**
+     * @param  boolean
+     */
     public function setNull($null)
     {
         $this->_null = $null;
index 7c47fc9..14358c6 100644 (file)
@@ -433,10 +433,11 @@ abstract class Horde_Db_Adapter_Base_Schema
         $limit     = isset($options['limit'])     ? $options['limit']     : null;
         $precision = isset($options['precision']) ? $options['precision'] : null;
         $scale     = isset($options['scale'])     ? $options['scale']     : null;
+        $unsigned  = isset($options['unsigned'])  ? $options['unsigned']  : null;
 
         $sql = 'ALTER TABLE ' . $this->quoteTableName($tableName) .
             ' ADD '.$this->quoteColumnName($columnName) .
-            ' '.$this->typeToSql($type, $limit, $precision, $scale);
+            ' '.$this->typeToSql($type, $limit, $precision, $scale, $unsigned);
         $sql = $this->addColumnOptions($sql, $options);
         return $this->execute($sql);
     }
@@ -660,7 +661,7 @@ abstract class Horde_Db_Adapter_Base_Schema
      * @param   string  $type
      * @param   string  $limit
      */
-    public function typeToSql($type, $limit=null, $precision=null, $scale=null)
+    public function typeToSql($type, $limit = null, $precision = null, $scale = null, $unsigned = null)
     {
         $natives = $this->nativeDatabaseTypes();
         $native = isset($natives[$type]) ? $natives[$type] : null;
@@ -678,10 +679,22 @@ abstract class Horde_Db_Adapter_Base_Schema
             }
         } else {
             $nativeLimit = is_array($native) ? $native['limit'] : null;
+
+            // If there is no explicit limit, adjust $nativeLimit for unsigned
+            // integers.
+            if (!empty($unsigned) && empty($limit) && is_integer($nativeLimit)) {
+                $nativeLimit--;
+            }
+
             if ($limit = !empty($limit) ? $limit : $nativeLimit) {
                 $sql .= "($limit)";
             }
         }
+
+        if (!empty($unsigned)) {
+            $sql .= ' UNSIGNED';
+        }
+
         return $sql;
     }
 
index 08cf300..edd9c79 100644 (file)
@@ -122,6 +122,7 @@ class Horde_Db_Adapter_Base_TableDefinition implements ArrayAccess, IteratorAggr
 
         $column->setPrecision(isset($opt['precision']) ? $opt['precision'] : null);
         $column->setScale(isset($opt['scale'])         ? $opt['scale']     : null);
+        $column->setUnsigned(isset($opt['unsigned'])   ? $opt['unsigned']  : null);
         $column->setDefault(isset($opt['default'])     ? $opt['default']   : null);
         $column->setNull(isset($opt['null'])           ? $opt['null']      : null);
 
index eb902fe..d0f3ec0 100644 (file)
@@ -337,8 +337,9 @@ class Horde_Db_Adapter_Mysql_Schema extends Horde_Db_Adapter_Base_Schema
         $limit     = !empty($options['limit'])     ? $options['limit']     : null;
         $precision = !empty($options['precision']) ? $options['precision'] : null;
         $scale     = !empty($options['scale'])     ? $options['scale']     : null;
+        $unsigned  = !empty($options['unsigned'])  ? $options['unsigned']  : null;
 
-        $typeSql = $this->typeToSql($type, $limit, $precision, $scale);
+        $typeSql = $this->typeToSql($type, $limit, $precision, $scale, $unsigned);
 
         $sql = "ALTER TABLE $quotedTableName CHANGE $quotedColumnName $quotedColumnName $typeSql";
         $sql = $this->addColumnOptions($sql, $options);
index bb212f2..3bf2180 100644 (file)
@@ -450,9 +450,10 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
         $limit     = isset($options['limit'])     ? $options['limit']     : null;
         $precision = isset($options['precision']) ? $options['precision'] : null;
         $scale     = isset($options['scale'])     ? $options['scale']     : null;
+        $unsigned  = isset($options['unsigned'])  ? $options['unsigned']  : null;
 
         // 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).' '.$this->typeToSql($type, $limit, $precision, $scale, $unsigned));
 
         $default = isset($options['default']) ? $options['default'] : null;
         $notnull = isset($options['null']) && $options['null'] === false;
@@ -472,11 +473,12 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
         $limit     = isset($options['limit'])     ? $options['limit']     : null;
         $precision = isset($options['precision']) ? $options['precision'] : null;
         $scale     = isset($options['scale'])     ? $options['scale']     : null;
+        $unsigned  = isset($options['unsigned'])  ? $options['unsigned']  : null;
 
         $quotedTableName = $this->quoteTableName($tableName);
 
         try {
-            $this->execute('ALTER TABLE '.$quotedTableName.' ALTER COLUMN '.$this->quoteColumnName($columnName).' TYPE '.$this->typeToSql($type, $limit, $precision, $scale));
+            $this->execute('ALTER TABLE '.$quotedTableName.' ALTER COLUMN '.$this->quoteColumnName($columnName).' TYPE '.$this->typeToSql($type, $limit, $precision, $scale, $unsigned));
         } catch (Horde_Db_Exception $e) {
             // This is PostgreSQL 7.x, or the old type could not be coerced to
             // the new type, so we have to use a more arcane way of doing it.
@@ -491,8 +493,9 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
                         break;
                     }
                 }
-                if ($oldType === null)
+                if ($oldType === null) {
                     throw new Horde_Db_Exception("$tableName does not have a column '$columnName'");
+                }
 
                 $this->beginDbTransaction();
 
@@ -500,9 +503,9 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
                 $this->addColumn($tableName, $tmpColumnName, $type, $options);
 
                 if ($oldType == 'boolean') {
-                    $this->execute('UPDATE '.$quotedTableName.' SET '.$this->quoteColumnName($tmpColumnName).' = CAST(CASE WHEN '.$this->quoteColumnName($columnName).' IS TRUE THEN 1 ELSE 0 END AS '.$this->typeToSql($type, $limit, $precision, $scale).')');
+                    $this->execute('UPDATE '.$quotedTableName.' SET '.$this->quoteColumnName($tmpColumnName).' = CAST(CASE WHEN '.$this->quoteColumnName($columnName).' IS TRUE THEN 1 ELSE 0 END AS '.$this->typeToSql($type, $limit, $precision, $scale, $unsigned).')');
                 } else {
-                    $this->execute('UPDATE '.$quotedTableName.' SET '.$this->quoteColumnName($tmpColumnName).' = CAST('.$this->quoteColumnName($columnName).' AS '.$this->typeToSql($type, $limit, $precision, $scale).')');
+                    $this->execute('UPDATE '.$quotedTableName.' SET '.$this->quoteColumnName($tmpColumnName).' = CAST('.$this->quoteColumnName($columnName).' AS '.$this->typeToSql($type, $limit, $precision, $scale, $unsigned).')');
                 }
 
                 $this->removeColumn($tableName, $columnName);
@@ -561,23 +564,25 @@ class Horde_Db_Adapter_Postgresql_Schema extends Horde_Db_Adapter_Base_Schema
     /**
      * Maps logical Rails types to PostgreSQL-specific data types.
      */
-    public function typeToSql($type, $limit = null, $precision = null, $scale = 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, $unsigned);
+
+        $unsigned = !empty($unsigned) ? ' UNSIGNED' : '';
 
         switch ($limit) {
         case 1:
         case 2:
-            return 'smallint';
+            return 'smallint' . $unsigned;
         case 3:
         case 4:
         case null:
-            return 'integer';
+            return 'integer' . $unsigned;
         case 5:
         case 6:
         case 7:
         case 8:
-            return 'bigint';
+            return 'bigint' . $unsigned;
         default:
             throw new Horde_Db_Exception("No integer type has byte size $limit. Use a numeric with precision 0 instead.");
         }
index 3975b40..615178c 100644 (file)
@@ -84,10 +84,25 @@ class Horde_Db_Adapter_Mysql_ColumnDefinitionTest extends PHPUnit_Framework_Test
         $this->assertEquals('`col_name` decimal(5, 2)', $col->toSql());
     }
 
+    public function testToSqlUnsigned()
+    {
+        $col = new Horde_Db_Adapter_Base_ColumnDefinition(
+            $this->_conn, 'col_name', 'integer', null, null, null, true
+        );
+        $this->assertEquals('`col_name` int(10) UNSIGNED', $col->toSql());
+
+        // set attribute instead
+        $col = new Horde_Db_Adapter_Base_ColumnDefinition(
+            $this->_conn, 'col_name', 'integer'
+        );
+        $col->setUnsigned(true);
+        $this->assertEquals('`col_name` int(10) UNSIGNED', $col->toSql());
+    }
+
     public function testToSqlNotNull()
     {
         $col = new Horde_Db_Adapter_Base_ColumnDefinition(
-            $this->_conn, 'col_name', 'string', null, null, null, null, false
+            $this->_conn, 'col_name', 'string', null, null, null, null, null, false
         );
         $this->assertEquals('`col_name` varchar(255) NOT NULL', $col->toSql());
 
@@ -102,7 +117,7 @@ class Horde_Db_Adapter_Mysql_ColumnDefinitionTest extends PHPUnit_Framework_Test
     public function testToSqlDefault()
     {
         $col = new Horde_Db_Adapter_Base_ColumnDefinition(
-            $this->_conn, 'col_name', 'string', null, null, null, 'test', null
+            $this->_conn, 'col_name', 'string', null, null, null, null, 'test'
         );
         $this->assertEquals("`col_name` varchar(255) DEFAULT 'test'", $col->toSql());
 
@@ -113,5 +128,4 @@ class Horde_Db_Adapter_Mysql_ColumnDefinitionTest extends PHPUnit_Framework_Test
         $col->setDefault('test');
         $this->assertEquals("`col_name` varchar(255) DEFAULT 'test'", $col->toSql());
     }
-
 }
index 0da0ba4..a301aba 100644 (file)
@@ -87,6 +87,13 @@ class Horde_Db_Adapter_Mysql_ColumnTest extends PHPUnit_Framework_TestCase
     {
         $col = new Horde_Db_Adapter_Mysql_Column('age', 'NULL', 'int(11)');
         $this->assertEquals('integer', $col->getType());
+        $this->assertFalse($col->isUnsigned());
+    }
+
+    public function testTypeIntegerUnsigned()
+    {
+        $col = new Horde_Db_Adapter_Mysql_Column('age', 'NULL', 'int(10) UNSIGNED');
+        $this->assertTrue($col->isUnsigned());
     }
 
     public function testTypeFloat()
index 3c40fc6..262483f 100644 (file)
@@ -77,10 +77,25 @@ class Horde_Db_Adapter_Postgresql_ColumnDefinitionTest extends PHPUnit_Framework
         $this->assertEquals('"col_name" decimal(5, 2)', $col->toSql());
     }
 
+    public function testToSqlUnsigned()
+    {
+        $col = new Horde_Db_Adapter_Base_ColumnDefinition(
+            $this->_conn, 'col_name', 'integer', null, null, null, true
+        );
+        $this->assertEquals('"col_name" integer UNSIGNED', $col->toSql());
+
+        // set attribute instead
+        $col = new Horde_Db_Adapter_Base_ColumnDefinition(
+            $this->_conn, 'col_name', 'integer'
+        );
+        $col->setUnsigned(true);
+        $this->assertEquals('"col_name" integer UNSIGNED', $col->toSql());
+    }
+
     public function testToSqlNotNull()
     {
         $col = new Horde_Db_Adapter_Base_ColumnDefinition(
-            $this->_conn, 'col_name', 'string', null, null, null, null, false
+            $this->_conn, 'col_name', 'string', null, null, null, null, null, false
         );
         $this->assertEquals('"col_name" character varying(255) NOT NULL', $col->toSql());
 
@@ -95,7 +110,7 @@ class Horde_Db_Adapter_Postgresql_ColumnDefinitionTest extends PHPUnit_Framework
     public function testToSqlDefault()
     {
         $col = new Horde_Db_Adapter_Base_ColumnDefinition(
-            $this->_conn, 'col_name', 'string', null, null, null, 'test', null
+            $this->_conn, 'col_name', 'string', null, null, null, null, 'test'
         );
         $this->assertEquals('"col_name" character varying(255) DEFAULT \'test\'', $col->toSql());
 
@@ -106,5 +121,4 @@ class Horde_Db_Adapter_Postgresql_ColumnDefinitionTest extends PHPUnit_Framework
         $col->setDefault('test');
         $this->assertEquals('"col_name" character varying(255) DEFAULT \'test\'', $col->toSql());
     }
-
 }
index 9981a71..5886725 100644 (file)
@@ -87,6 +87,13 @@ class Horde_Db_Adapter_Postgresql_ColumnTest extends PHPUnit_Framework_TestCase
     {
         $col = new Horde_Db_Adapter_Postgresql_Column('age', 'NULL', 'int(11)');
         $this->assertEquals('integer', $col->getType());
+        $this->assertFalse($col->isUnsigned());
+    }
+
+    public function testTypeIntegerUnsigned()
+    {
+        $col = new Horde_Db_Adapter_Postgresql_Column('age', 'NULL', 'integer UNSIGNED');
+        $this->assertTrue($col->isUnsigned());
     }
 
     public function testTypeFloat()
index 56f2265..d1f8f98 100644 (file)
@@ -84,10 +84,25 @@ class Horde_Db_Adapter_Sqlite_ColumnDefinitionTest extends PHPUnit_Framework_Tes
         $this->assertEquals('"col_name" decimal(5, 2)', $col->toSql());
     }
 
+    public function testToSqlUnsigned()
+    {
+        $col = new Horde_Db_Adapter_Base_ColumnDefinition(
+            $this->_conn, 'col_name', 'integer', null, null, null, true
+        );
+        $this->assertEquals('"col_name" int UNSIGNED', $col->toSql());
+
+        // set attribute instead
+        $col = new Horde_Db_Adapter_Base_ColumnDefinition(
+            $this->_conn, 'col_name', 'integer'
+        );
+        $col->setUnsigned(true);
+        $this->assertEquals('"col_name" int UNSIGNED', $col->toSql());
+    }
+
     public function testToSqlNotNull()
     {
         $col = new Horde_Db_Adapter_Base_ColumnDefinition(
-            $this->_conn, 'col_name', 'string', null, null, null, null, false
+            $this->_conn, 'col_name', 'string', null, null, null, null, null, false
         );
         $this->assertEquals('"col_name" varchar(255) NOT NULL', $col->toSql());
 
@@ -97,12 +112,19 @@ class Horde_Db_Adapter_Sqlite_ColumnDefinitionTest extends PHPUnit_Framework_Tes
         );
         $col->setNull(false);
         $this->assertEquals('"col_name" varchar(255) NOT NULL', $col->toSql());
+
+        // set attribute to the default (true)
+        $col = new Horde_Db_Adapter_Base_ColumnDefinition(
+            $this->_conn, 'col_name', 'string'
+        );
+        $col->setNull(true);
+        $this->assertEquals('"col_name" varchar(255)', $col->toSql());
     }
 
     public function testToSqlDefault()
     {
         $col = new Horde_Db_Adapter_Base_ColumnDefinition(
-            $this->_conn, 'col_name', 'string', null, null, null, 'test', null
+            $this->_conn, 'col_name', 'string', null, null, null, null, 'test', null
         );
         $this->assertEquals('"col_name" varchar(255) DEFAULT \'test\'', $col->toSql());
 
index 23eec8e..2d4d139 100644 (file)
@@ -87,6 +87,13 @@ class Horde_Db_Adapter_Sqlite_ColumnTest extends PHPUnit_Framework_TestCase
     {
         $col = new Horde_Db_Adapter_Sqlite_Column('age', 'NULL', 'int(11)');
         $this->assertEquals('integer', $col->getType());
+        $this->assertFalse($col->isUnsigned());
+    }
+
+    public function testTypeIntegerUnsigned()
+    {
+        $col = new Horde_Db_Adapter_Sqlite_Column('age', 'NULL', 'int UNSIGNED');
+        $this->assertTrue($col->isUnsigned());
     }
 
     public function testTypeFloat()