From: Chuck Hagenbuch Date: Sun, 13 Dec 2009 17:06:35 +0000 (-0500) Subject: Merge branch 'db-migrations' X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=c43180a2f940f4918250fd5d38e43ba83cc0453c;p=horde.git Merge branch 'db-migrations' Conflicts: framework/Db/lib/Horde/Db/Adapter/Base/TableDefinition.php framework/Db/test/Horde/Db/AllTests.php --- c43180a2f940f4918250fd5d38e43ba83cc0453c diff --cc framework/Db/lib/Horde/Db/Adapter/Base.php index 1d05a681c,000000000..59d72a70c mode 100644,000000..100644 --- a/framework/Db/lib/Horde/Db/Adapter/Base.php +++ b/framework/Db/lib/Horde/Db/Adapter/Base.php @@@ -1,623 -1,0 +1,623 @@@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Adapter + */ + +/** + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Adapter + */ +abstract class Horde_Db_Adapter_Base +{ + /** + * Config options + * @var array + */ + protected $_config = array(); + + /** + * @var mixed + */ + protected $_connection = null; + + /** + * @var boolean + */ + protected $_transactionStarted = false; + + /** + * @var int + */ + protected $_rowCount = null; + + /** + * @var int + */ + protected $_runtime = null; + + /** + * @var boolean + */ + protected $_active = null; + + /** + * @var Cache object + */ + protected $_cache = null; + + /** + * @var Logger + */ + protected $_logger = null; + + /** + * @var Horde_Db_Adapter_Base_Schema + */ + protected $_schema = null; + + /** + * @var string + */ + protected $_schemaClass = null; + + /** + * @var array + */ + protected $_schemaMethods = array(); + + + /*########################################################################## + # Construct/Destruct + ##########################################################################*/ + + /** + * @param array $config Configuration options and optional objects (logger, + * cache, etc.) + */ + public function __construct($config) + { + // Create a stub if we don't have a useable cache. + if (isset($config['cache']) + && is_callable(array($config['cache'], 'get')) + && is_callable(array($config['cache'], 'set'))) { + $this->_cache = $config['cache']; + unset($config['cache']); + } else { + $this->_cache = new Horde_Support_Stub; + } + + // Create a stub if we don't have a useable logger. + if (isset($config['logger']) + && is_callable(array($config['logger'], 'log'))) { + $this->_logger = $config['logger']; + unset($config['logger']); + } else { + $this->_logger = new Horde_Support_Stub; + } + + // Default to UTF-8 + if (!isset($config['charset'])) { + $config['charset'] = 'UTF-8'; + } + + $this->_config = $config; + $this->_runtime = 0; + + // Create the database-specific (but not adapter specific) schema + // object. + if (!$this->_schemaClass) + $this->_schemaClass = get_class($this).'_Schema'; + $this->_schema = new $this->_schemaClass($this, array( + 'cache' => $this->_cache, + 'logger' => $this->_logger)); + $this->_schemaMethods = array_flip(get_class_methods($this->_schema)); + + $this->connect(); + } + + /** + * Free any resources that are open. + */ + public function __destruct() + { + $this->disconnect(); + } + + + /*########################################################################## + # Object factory + ##########################################################################*/ + + /** + * Delegate calls to the schema object. + * + * @param string $method + * @param array $args + */ + public function componentFactory($component, $args) + { + $class = str_replace('_Schema', '', $this->_schemaClass) . '_' . $component; + if (class_exists($class)) { + $class = new ReflectionClass($class); + } else { + $class = new ReflectionClass('Horde_Db_Adapter_Base_' . $component); + } + + return $class->newInstanceArgs($args); + } + + + /*########################################################################## + # Object composition + ##########################################################################*/ + + /** + * Delegate calls to the schema object. + * + * @param string $method + * @param array $args + */ + public function __call($method, $args) + { + if (isset($this->_schemaMethods[$method])) { + return call_user_func_array(array($this->_schema, $method), $args); + } + + throw new BadMethodCallException('Call to undeclared method "'.$method.'"'); + } + + + /*########################################################################## + # Public + ##########################################################################*/ + + /** + * Returns the human-readable name of the adapter. Use mixed case - one + * can always use downcase if needed. + * + * @return string + */ + public function adapterName() + { + return 'Base'; + } + + /** + * Does this adapter support migrations? Backend specific, as the + * abstract adapter always returns +false+. + * + * @return boolean + */ + public function supportsMigrations() + { + return false; + } + + /** + * Does this adapter support using DISTINCT within COUNT? This is +true+ + * for all adapters except sqlite. + * + * @return boolean + */ + public function supportsCountDistinct() + { + return true; + } + + /** + * Should primary key values be selected from their corresponding + * sequence before the insert statement? If true, next_sequence_value + * is called before each insert to set the record's primary key. + * This is false for all adapters but Firebird. + */ + public function prefetchPrimaryKey($tableName = null) + { + return false; + } + + /** + * Reset the timer + * + * @return int + */ + public function resetRuntime() + { + $runtime = $this->_runtime; + $this->_runtime = 0; + return $this->_runtime; + } + + + /*########################################################################## + # Connection Management + ##########################################################################*/ + + /** + * Connect to the db + */ + abstract public function connect(); + + /** + * Is the connection active + * + * @return boolean + */ + public function isActive() + { + return $this->_active; + } + + /** + * Reconnect to the db + */ + public function reconnect() + { + $this->disconnect(); + $this->connect(); + } + + /** + * Disconnect from db + */ + public function disconnect() + { + $this->_connection = null; + $this->_active = false; + } + + /** + * Provides access to the underlying database connection. Useful for when + * you need to call a proprietary method such as postgresql's lo_* methods + * + * @return resource + */ + public function rawConnection() + { + return $this->_connection; + } + + + /*########################################################################## + # Database Statements + ##########################################################################*/ + + /** + * Returns an array of records with the column names as keys, and + * column values as values. + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + * @return Traversable + */ + public function select($sql, $arg1 = null, $arg2 = null) + { + return $this->execute($sql, $arg1, $arg2); + } + + /** + * Returns an array of record hashes with the column names as keys and + * column values as values. + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + */ + public function selectAll($sql, $arg1 = null, $arg2 = null) + { + $rows = array(); + $result = $this->select($sql, $arg1, $arg2); + if ($result) { + foreach ($result as $row) { + $rows[] = $row; + } + } + return $rows; + } + + /** + * Returns a record hash with the column names as keys and column values + * as values. + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + * @return array + */ + public function selectOne($sql, $arg1 = null, $arg2 = null) + { + $result = $this->selectAll($sql, $arg1, $arg2); + return $result ? next($result) : array(); + } + + /** + * Returns a single value from a record + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + * @return string + */ + public function selectValue($sql, $arg1 = null, $arg2 = null) + { + $result = $this->selectOne($sql, $arg1, $arg2); + return $result ? next($result) : null; + } + + /** + * Returns an array of the values of the first column in a select: + * selectValues("SELECT id FROM companies LIMIT 3") => [1,2,3] + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + */ + public function selectValues($sql, $arg1 = null, $arg2 = null) + { + $result = $this->selectAll($sql, $arg1, $arg2); + $values = array(); + foreach ($result as $row) { + $values[] = next($row); + } + return $values; + } + + /** + * Returns an array where the keys are the first column of a select, and the + * values are the second column: + * + * selectAssoc("SELECT id, name FROM companies LIMIT 3") => [1 => 'Ford', 2 => 'GM', 3 => 'Chrysler'] + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + */ + public function selectAssoc($sql, $arg1 = null, $arg2 = null) + { + $result = $this->selectAll($sql, $arg1, $arg2); + $values = array(); + foreach ($result as $row) { + $values[next($row)] = next($row); + } + return $values; + } + + /** + * Executes the SQL statement in the context of this connection. + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + */ + public function execute($sql, $arg1 = null, $arg2 = null) + { + if (is_array($arg1)) { + $sql = $this->_replaceParameters($sql, $arg1); + $name = $arg2; + } else { + $name = $arg1; + } + + $t = new Horde_Support_Timer; + $t->push(); + + try { + $stmt = $this->_connection->query($sql); + } catch (Exception $e) { + $this->_logInfo($sql, 'QUERY FAILED: ' . $e->getMessage()); + $this->_logInfo($sql, $name); + throw new Horde_Db_Exception((string)$e->getMessage(), (int)$e->getCode()); + } + + $this->_logInfo($sql, $name, $t->pop()); + + $this->_rowCount = $stmt ? $stmt->rowCount() : 0; + return $stmt; + } + + /** + * Returns the last auto-generated ID from the affected table. + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + * @param string $pk + * @param int $idValue + * @param string $sequenceName + */ + public function insert($sql, $arg1 = null, $arg2 = null, $pk = null, $idValue = null, $sequenceName = null) + { + $this->execute($sql, $arg1, $arg2); + return isset($idValue) ? $idValue : $this->_connection->lastInsertId(); + } + + /** + * Executes the update statement and returns the number of rows affected. + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + */ + public function update($sql, $arg1 = null, $arg2 = null) + { + $this->execute($sql, $arg1, $arg2); + return $this->_rowCount; + } + + /** + * Executes the delete statement and returns the number of rows affected. + * + * @param string $sql + * @param mixed $arg1 Either an array of bound parameters or a query name. + * @param string $arg2 If $arg1 contains bound parameters, the query name. + */ + public function delete($sql, $arg1 = null, $arg2 = null) + { + $this->execute($sql, $arg1, $arg2); + return $this->_rowCount; + } + + /** + * Check if a transaction has been started + */ + public function transactionStarted() + { + return $this->_transactionStarted; + } + + /** + * Begins the transaction (and turns off auto-committing). + */ + public function beginDbTransaction() + { + $this->_transactionStarted = true; + $this->_connection->beginTransaction(); + } + + /** + * Commits the transaction (and turns on auto-committing). + */ + public function commitDbTransaction() + { + $this->_connection->commit(); + $this->_transactionStarted = false; + } + + /** + * Rolls back the transaction (and turns on auto-committing). Must be + * done if the transaction block raises an exception or returns false. + */ + public function rollbackDbTransaction() + { + if (! $this->_transactionStarted) { return; } + + $this->_connection->rollBack(); + $this->_transactionStarted = false; + } + + /** + * Appends +LIMIT+ and +OFFSET+ options to a SQL statement. + * + * @param string $sql + * @param array $options + * @return string + */ + public function addLimitOffset($sql, $options) + { + if (isset($options['limit']) && $limit = $options['limit']) { + if (isset($options['offset']) && $offset = $options['offset']) { + $sql .= " LIMIT $offset, $limit"; + } else { + $sql .= " LIMIT $limit"; + } + } + return $sql; + } + + public function sanitizeLimit($limit) + { + if (strpos($limit, ',') !== false) { + return implode(',', array_map(create_function('$i', 'return (int)$i;'), explode(',', $limit))); + } else return (int)$limit; + } + + /** + * Appends a locking clause to an SQL statement. + * This method *modifies* the +sql+ parameter. + * # SELECT * FROM suppliers FOR UPDATE + * add_lock! 'SELECT * FROM suppliers', :lock => true + * add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE' + */ + public function addLock(&$sql, $options = array()) + { + if (isset($options['lock']) && is_string($options['lock'])) { + $sql .= ' ' . $lock; + } else { + $sql .= ' FOR UPDATE'; + } + } + + /** + * Inserts the given fixture into the table. Overridden in adapters that + * require something beyond a simple insert (eg. Oracle). + */ + public function insertFixture($fixture, $tableName) + { + /*@TODO*/ + return $this->execute("INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'); + } + + public function emptyInsertStatement($tableName) + { + return 'INSERT INTO '.$this->quoteTableName($tableName).' VALUES(DEFAULT)'; + } + + + /*########################################################################## + # Protected + ##########################################################################*/ + + /** + * Replace ? in a SQL statement with quoted values from $args + * + * @param string $sql + * @param array $args + */ + protected function _replaceParameters($sql, $args) + { + $paramCount = substr_count($sql, '?'); + if (count($args) != $paramCount) { + throw new Horde_Db_Exception('Parameter count mismatch'); + } + + $sqlPieces = explode('?', $sql); + $sql = array_shift($sqlPieces); + while (count($sqlPieces)) { + $sql .= $this->quote(array_shift($args)) . array_shift($sqlPieces); + } + return $sql; + } + + /** + * Logs the SQL query for debugging. + * + * @param string $sql + * @param string $name + * @param float $runtime + */ + protected function _logInfo($sql, $name, $runtime = null) + { + /*@TODO */ + $name = (empty($name) ? '' : $name) - . (empty($runtime) ? '' : " ($runtime ms)"); ++ . (empty($runtime) ? '' : sprintf(" (%.4fs)", $runtime)); + $this->_logger->info($this->_formatLogEntry($name, $sql)); + } + + /** + * Formats the log entry. + * + * @param string $message + * @param string $sql + */ + protected function _formatLogEntry($message, $sql) + { + $sql = preg_replace("/\s+/", ' ', $sql); + $sql = "\n\t".wordwrap($sql, 70, "\n\t ", 1); + return "SQL $message $sql"; + } + +} diff --cc framework/Db/lib/Horde/Db/Adapter/Base/Column.php index f2f0c515d,000000000..5d0ce69bd mode 100644,000000..100644 --- a/framework/Db/lib/Horde/Db/Adapter/Base/Column.php +++ b/framework/Db/lib/Horde/Db/Adapter/Base/Column.php @@@ -1,359 -1,0 +1,360 @@@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Adapter + */ + +/** + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Adapter + */ +class Horde_Db_Adapter_Base_Column +{ + protected $_name; + protected $_type; + protected $_null; + protected $_limit; + protected $_precision; + protected $_scale; + protected $_default; + protected $_sqlType; + protected $_isText; + protected $_isNumber; + + + /*########################################################################## + # Construct/Destruct + ##########################################################################*/ + + /** + * Construct + * + * @param string $name The column's name, such as supplier_id in supplier_id int(11). + * @param string $default The type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'. + * @param string $sqlType Only used to extract the column's length, if necessary. For example +60+ in company_name varchar(60). + * @param boolean $null Determines if this column allows +NULL+ values. + */ + public function __construct($name, $default, $sqlType = null, $null = true) + { + $this->_name = $name; + $this->_sqlType = $sqlType; + $this->_null = $null; ++ ++ $this->_type = $this->_simplifiedType($sqlType); ++ $this->_isText = $this->_type == 'text' || $this->_type == 'string'; ++ $this->_isNumber = $this->_type == 'float' || $this->_type == 'integer' || $this->_type == 'decimal'; ++ + $this->_limit = $this->_extractLimit($sqlType); + $this->_precision = $this->_extractPrecision($sqlType); + $this->_scale = $this->_extractScale($sqlType); - $this->_type = $this->_simplifiedType($sqlType); + $this->_default = $this->extractDefault($default); - - $this->_isText = $this->_type == 'text' || $this->_type == 'string'; - $this->_isNumber = $this->_type == 'float' || $this->_type == 'integer' || $this->_type == 'decimal'; + } + + /** + * @return boolean + */ + public function isText() + { + return $this->_isText; + } + + /** + * @return boolean + */ + public function isNumber() + { + return $this->_isNumber; + } + + /** + * Casts value (which is a String) to an appropriate instance. + */ + public function typeCast($value) + { + if ($value === null) return null; + + switch ($this->_type) { + case 'string': + case 'text': + return $value; + case 'integer': + return strlen($value) ? (int)$value : null; + case 'float': + return strlen($value) ? (float)$value : null; + case 'decimal': + return $this->valueToDecimal($value); + case 'datetime': + case 'timestamp': + return $this->stringToTime($value); + case 'time': + return $this->stringToDummyTime($value); + case 'date': + return $this->stringToDate($value); + case 'binary': + return $this->binaryToString($value); + case 'boolean': + return $this->valueToBoolean($value); + default: + return $value; + } + } + + public function extractDefault($default) + { + return $this->typeCast($default); + } + + + /*########################################################################## + # Accessor + ##########################################################################*/ + + /** + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * @return string + */ + public function getDefault() + { + return $this->_default; + } + + /** + * @return string + */ + public function getType() + { + return $this->_type; + } + + /** + * @return int + */ + public function getLimit() + { + return $this->_limit; + } + + /** + * @return int + */ + public function precision() + { + return $this->_precision; + } + + /** + * @return int + */ + public function scale() + { + return $this->_scale; + } + + /** + * @return boolean + */ + public function isNull() + { + return $this->_null; + } + + /** + * @return string + */ + public function getSqlType() + { + return $this->_sqlType; + } + + + /*########################################################################## + # Type Juggling + ##########################################################################*/ + + /** + * Used to convert from Strings to BLOBs + * + * @return string + */ + public function stringToBinary($value) + { + return $value; + } + + /** + * Used to convert from BLOBs to Strings + * + * @return string + */ + public function binaryToString($value) + { + return $value; + } + + /** + * @param string $string + * @return Horde_Date + */ + public function stringToDate($string) + { + if (empty($string) || + // preserve '0000-00-00' (http://bugs.php.net/bug.php?id=45647) + preg_replace('/[^\d]/', '', $string) == 0) { + return null; + } + + $d = new Horde_Date($string); + $d->setDefaultFormat('Y-m-d'); + + return $d; + } + + /** + * @param string $string + * @return Horde_Date + */ + public function stringToTime($string) + { + if (empty($string) || + // preserve '0000-00-00 00:00:00' (http://bugs.php.net/bug.php?id=45647) + preg_replace('/[^\d]/', '', $string) == 0) { + return null; + } + + return new Horde_Date($string); + } + + /** + * @TODO Return a Horde_Date object instead? + * + * @param string $string + * @return Horde_Date + */ + public function stringToDummyTime($value) + { + if (empty($string)) { + return null; + } + return $this->stringToTime('2000-01-01 ' . $string); + } + + /** + * @param mixed $value + * @return boolean + */ + public function valueToBoolean($value) + { + if ($value === true || $value === false) { + return $value; + } + + $value = strtolower($value); + return $value == 'true' || $value == 't' || $value == '1'; + } + + /** + * @param mixed $value + * @return decimal + */ + public function valueToDecimal($value) + { + return (float)$value; + } + + + /*########################################################################## + # Protected + ##########################################################################*/ + + /** + * @param string $sqlType + * @return int + */ + protected function _extractLimit($sqlType) + { + if (preg_match("/\((.*)\)/", $sqlType, $matches)) { + return (int)$matches[1]; + } + return null; + } + + /** + * @param string $sqlType + * @return int + */ + protected function _extractPrecision($sqlType) + { + if (preg_match("/^(numeric|decimal|number)\((\d+)(,\d+)?\)/i", $sqlType, $matches)) { + return (int)$matches[2]; + } + return null; + } + + /** + * @param string $sqlType + * @return int + */ + protected function _extractScale($sqlType) + { + switch (true) { + case preg_match("/^(numeric|decimal|number)\((\d+)\)/i", $sqlType): + return 0; + case preg_match("/^(numeric|decimal|number)\((\d+)(,(\d+))\)/i", + $sqlType, $match): + return (int)$match[4]; + } + } + + /** + * @param string $fieldType + * @return string + */ + protected function _simplifiedType($fieldType) + { + switch (true) { + case preg_match('/int/i', $fieldType): + return 'integer'; + case preg_match('/float|double/i', $fieldType): + return 'float'; + case preg_match('/decimal|numeric|number/i', $fieldType): + return $this->_scale == 0 ? 'integer' : 'decimal'; + case preg_match('/datetime/i', $fieldType): + return 'datetime'; + case preg_match('/timestamp/i', $fieldType): + return 'timestamp'; + case preg_match('/time/i', $fieldType): + return 'time'; + case preg_match('/date/i', $fieldType): + return 'date'; + case preg_match('/clob|text/i', $fieldType): + return 'text'; + case preg_match('/blob|binary/i', $fieldType): + return 'binary'; + case preg_match('/char|string/i', $fieldType): + return 'string'; + case preg_match('/boolean/i', $fieldType): + return 'boolean'; + } + } + +} diff --cc framework/Db/lib/Horde/Db/Adapter/Base/TableDefinition.php index a5af543a1,000000000..e27ee1b40 mode 100644,000000..100644 --- a/framework/Db/lib/Horde/Db/Adapter/Base/TableDefinition.php +++ b/framework/Db/lib/Horde/Db/Adapter/Base/TableDefinition.php @@@ -1,235 -1,0 +1,245 @@@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Adapter + */ + +/** + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_Db + * @subpackage Adapter + */ - class Horde_Db_Adapter_Base_TableDefinition implements ArrayAccess ++class Horde_Db_Adapter_Base_TableDefinition implements ArrayAccess, IteratorAggregate +{ + protected $_name = null; + protected $_base = null; + protected $_options = null; + protected $_columns = null; + + /** + * Class Constructor + * + * @param string $name + * @param Horde_Db_Adapter_Base_Schema $base + * @param array $options + */ + public function __construct($name, $base, $options=array()) + { + $this->_name = $name; + $this->_base = $base; + $this->_options = $options; + $this->_columns = array(); + } + + /** + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->_options; + } + - /** ++ /**v + * @param string $name + */ + public function primaryKey($name) + { + $natives = $this->_native(); + $this->column($name, $natives['primaryKey']); + } + + /** + * Instantiates a new column for the table. + * The +type+ parameter must be one of the following values: + * :primary_key, :string, :text, + * :integer, :float, :datetime, + * :timestamp, :time, :date, + * :binary, :boolean. + * + * Available options are (none of these exists by default): + * * :limit: + * Requests a maximum column length (:string, :text, + * :binary or :integer columns only) + * * :default: + * The column's default value. You cannot explicitely set the default + * value to +NULL+. Simply leave off this option if you want a +NULL+ + * default value. + * * :null: + * Allows or disallows +NULL+ values in the column. This option could + * have been named :null_allowed. + * + * This method returns self. + * + * ===== Examples + * # Assuming def is an instance of TableDefinition + * def.column(:granted, :boolean) + * #=> granted BOOLEAN + * + * def.column(:picture, :binary, :limit => 2.megabytes) + * #=> picture BLOB(2097152) + * + * def.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false) + * #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL + * + * @return TableDefinition + */ + public function column($name, $type, $options=array()) + { + if ($this[$name]) { + $column = $this[$name]; + } else { + $column = $this->_base->componentFactory('ColumnDefinition', array( + $this->_base, $name, $type)); + } + + $natives = $this->_native(); + $opt = $options; + + if (isset($opt['limit']) || isset($natives[$type])) { + $nativeLimit = isset($natives[$type]['limit']) ? $natives[$type]['limit'] : null; + $column->setLimit(isset($opt['limit']) ? $opt['limit'] : $nativeLimit); + } + + $column->setPrecision(isset($opt['precision']) ? $opt['precision'] : null); + $column->setScale(isset($opt['scale']) ? $opt['scale'] : null); + $column->setDefault(isset($opt['default']) ? $opt['default'] : null); + $column->setNull(isset($opt['null']) ? $opt['null'] : null); + + $this[$name] ? $this[$name] = $column : $this->_columns[] = $column; + return $this; + } + + /** + * Wrap up table creation block & create the table + */ + public function end() + { + return $this->_base->endTable($this); + } + + /** + * Returns a String whose contents are the column definitions + * concatenated together. This string can then be pre and appended to + * to generate the final SQL to create the table. + * + * @return string + */ + public function toSql() + { + $cols = array(); + foreach ($this->_columns as $col) { $cols[] = $col->toSql(); } + + return " ".implode(", \n ", $cols); + } + + + /*########################################################################## + # ArrayAccess + ##########################################################################*/ + + /** + * ArrayAccess: Check if the given offset exists + * + * @param int $offset + * @return boolean + */ + public function offsetExists($offset) + { + foreach ($this->_columns as $column) { + if ($column->getName() == $offset) return true; + } + return false; + } + + /** + * ArrayAccess: Return the value for the given offset. + * + * @param int $offset + * @return object {@link {@Horde_Db_Adapter_Base_ColumnDefinition} + */ + public function offsetGet($offset) + { + if (!$this->offsetExists($offset)) { + return null; + } + + foreach ($this->_columns as $column) { + if ($column->getName() == $offset) { + return $column; + } + } + } + + /** + * ArrayAccess: Set value for given offset + * + * @param int $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + foreach ($this->_columns as $key=>$column) { + if ($column->getName() == $offset) { + $this->_columns[$key] = $value; + } + } + } + + /** + * ArrayAccess: remove element + * + * @param int $offset + */ + public function offsetUnset($offset) + { + foreach ($this->_columns as $key=>$column) { + if ($column->getName() == $offset) { + unset($this->_columns[$key]); + } + } + } + + + /*########################################################################## ++ # ArrayAccess ++ ##########################################################################*/ ++ ++ public function getIterator() ++ { ++ return new ArrayIterator($this->_columns); ++ } ++ ++ ++ /*########################################################################## + # Protected + ##########################################################################*/ + + /** + * Get the types + */ + protected function _native() + { + return $this->_base->nativeDatabaseTypes(); + } + +}