diff --git a/lib/Doctrine/DataDict/Pgsql.php b/lib/Doctrine/DataDict/Pgsql.php index bdde8344f..3f1aaf492 100644 --- a/lib/Doctrine/DataDict/Pgsql.php +++ b/lib/Doctrine/DataDict/Pgsql.php @@ -1,621 +1,621 @@ -. - */ -Doctrine::autoload('Doctrine_DataDict'); -/** - * @package Doctrine - * @subpackage Doctrine_DataDict - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @author Konsta Vesterinen - * @author Paul Cooper - * @author Lukas Smith (PEAR MDB2 library) - * @version $Revision$ - * @category Object Relational Mapping - * @link www.phpdoctrine.com - * @since 1.0 - */ -class Doctrine_DataDict_Pgsql extends Doctrine_DataDict -{ - /** - * @param array $reservedKeyWords an array of reserved keywords by pgsql - */ - protected static $reservedKeyWords = array( - 'abort', - 'absolute', - 'access', - 'action', - 'add', - 'after', - 'aggregate', - 'all', - 'alter', - 'analyse', - 'analyze', - 'and', - 'any', - 'as', - 'asc', - 'assertion', - 'assignment', - 'at', - 'authorization', - 'backward', - 'before', - 'begin', - 'between', - 'bigint', - 'binary', - 'bit', - 'boolean', - 'both', - 'by', - 'cache', - 'called', - 'cascade', - 'case', - 'cast', - 'chain', - 'char', - 'character', - 'characteristics', - 'check', - 'checkpoint', - 'class', - 'close', - 'cluster', - 'coalesce', - 'collate', - 'column', - 'comment', - 'commit', - 'committed', - 'constraint', - 'constraints', - 'conversion', - 'convert', - 'copy', - 'create', - 'createdb', - 'createuser', - 'cross', - 'current_date', - 'current_time', - 'current_timestamp', - 'current_user', - 'cursor', - 'cycle', - 'database', - 'day', - 'deallocate', - 'dec', - 'decimal', - 'declare', - 'default', - 'deferrable', - 'deferred', - 'definer', - 'delete', - 'delimiter', - 'delimiters', - 'desc', - 'distinct', - 'do', - 'domain', - 'double', - 'drop', - 'each', - 'else', - 'encoding', - 'encrypted', - 'end', - 'escape', - 'except', - 'exclusive', - 'execute', - 'exists', - 'explain', - 'external', - 'extract', - 'false', - 'fetch', - 'float', - 'for', - 'force', - 'foreign', - 'forward', - 'freeze', - 'from', - 'full', - 'function', - 'get', - 'global', - 'grant', - 'group', - 'handler', - 'having', - 'hour', - 'ilike', - 'immediate', - 'immutable', - 'implicit', - 'in', - 'increment', - 'index', - 'inherits', - 'initially', - 'inner', - 'inout', - 'input', - 'insensitive', - 'insert', - 'instead', - 'int', - 'integer', - 'intersect', - 'interval', - 'into', - 'invoker', - 'is', - 'isnull', - 'isolation', - 'join', - 'key', - 'lancompiler', - 'language', - 'leading', - 'left', - 'level', - 'like', - 'limit', - 'listen', - 'load', - 'local', - 'localtime', - 'localtimestamp', - 'location', - 'lock', - 'match', - 'maxvalue', - 'minute', - 'minvalue', - 'mode', - 'month', - 'move', - 'names', - 'national', - 'natural', - 'nchar', - 'new', - 'next', - 'no', - 'nocreatedb', - 'nocreateuser', - 'none', - 'not', - 'nothing', - 'notify', - 'notnull', - 'null', - 'nullif', - 'numeric', - 'of', - 'off', - 'offset', - 'oids', - 'old', - 'on', - 'only', - 'operator', - 'option', - 'or', - 'order', - 'out', - 'outer', - 'overlaps', - 'overlay', - 'owner', - 'partial', - 'password', - 'path', - 'pendant', - 'placing', - 'position', - 'precision', - 'prepare', - 'primary', - 'prior', - 'privileges', - 'procedural', - 'procedure', - 'read', - 'real', - 'recheck', - 'references', - 'reindex', - 'relative', - 'rename', - 'replace', - 'reset', - 'restrict', - 'returns', - 'revoke', - 'right', - 'rollback', - 'row', - 'rule', - 'schema', - 'scroll', - 'second', - 'security', - 'select', - 'sequence', - 'serializable', - 'session', - 'session_user', - 'set', - 'setof', - 'share', - 'show', - 'similar', - 'simple', - 'smallint', - 'some', - 'stable', - 'start', - 'statement', - 'statistics', - 'stdin', - 'stdout', - 'storage', - 'strict', - 'substring', - 'sysid', - 'table', - 'temp', - 'template', - 'temporary', - 'then', - 'time', - 'timestamp', - 'to', - 'toast', - 'trailing', - 'transaction', - 'treat', - 'trigger', - 'trim', - 'true', - 'truncate', - 'trusted', - 'type', - 'unencrypted', - 'union', - 'unique', - 'unknown', - 'unlisten', - 'until', - 'update', - 'usage', - 'user', - 'using', - 'vacuum', - 'valid', - 'validator', - 'values', - 'varchar', - 'varying', - 'verbose', - 'version', - 'view', - 'volatile', - 'when', - 'where', - 'with', - 'without', - 'work', - 'write', - 'year', - 'zone' - ); - - /** - * Obtain DBMS specific SQL code portion needed to declare an text type - * field to be used in statements like CREATE TABLE. - * - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - */ - public function getNativeDeclaration(array $field) - { - if ( ! isset($field['type'])) { - throw new Doctrine_DataDict_Exception('Missing column type.'); - } - switch ($field['type']) { - case 'char': - case 'string': - case 'array': - case 'object': - case 'varchar': - case 'gzip': - $length = (isset($field['length']) && $field['length']) ? $field['length'] : null; - // TODO: $this->conn->options['default_text_field_length']; - - $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; - - return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$this->conn->options['default_text_field_length'].')') - : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); - - case 'clob': - return 'TEXT'; - case 'blob': - return 'BYTEA'; - case 'enum': - case 'integer': - case 'int': - if (!empty($field['autoincrement'])) { - if (!empty($field['length'])) { - $length = $field['length']; - if ($length > 4) { - return 'BIGSERIAL'; - } - } - return 'SERIAL'; - } - if (!empty($field['length'])) { - $length = $field['length']; - if ($length <= 2) { - return 'SMALLINT'; - } elseif ($length == 3 || $length == 4) { - return 'INT'; - } elseif ($length > 4) { - return 'BIGINT'; - } - } - return 'INT'; - case 'boolean': - return 'BOOLEAN'; - case 'date': - return 'DATE'; - case 'time': - return 'TIME without time zone'; - case 'timestamp': - return 'TIMESTAMP without time zone'; - case 'float': - case 'double': - return 'FLOAT8'; - case 'decimal': - $length = !empty($field['length']) ? $field['length'] : 18; - $scale = !empty($field['scale']) ? $field['scale'] : $this->conn->getAttribute(Doctrine::ATTR_DECIMAL_PLACES); - return 'NUMERIC('.$length.','.$scale.')'; - } - throw new Doctrine_DataDict_Exception('Unknown field type \'' . $field['type'] . '\'.'); - } - /** - * Maps a native array description of a field to a portable Doctrine datatype and length - * - * @param array $field native field description - * - * @return array containing the various possible types, length, sign, fixed - */ - public function getPortableDeclaration(array $field) - { - - $length = (isset($field['length'])) ? $field['length'] : null; - if ($length == '-1' && isset($field['atttypmod'])) { - $length = $field['atttypmod'] - 4; - } - if ((int)$length <= 0) { - $length = null; - } - $type = array(); - $unsigned = $fixed = null; - - if ( ! isset($field['name'])) { - $field['name'] = ''; - } - - $dbType = strtolower($field['type']); - - switch ($dbType) { - case 'smallint': - case 'int2': - $type[] = 'integer'; - $unsigned = false; - $length = 2; - if ($length == '2') { - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - } - break; - case 'int': - case 'int4': - case 'integer': - case 'serial': - case 'serial4': - $type[] = 'integer'; - $unsigned = false; - $length = 4; - break; - case 'bigint': - case 'int8': - case 'bigserial': - case 'serial8': - $type[] = 'integer'; - $unsigned = false; - $length = 8; - break; - case 'bool': - case 'boolean': - $type[] = 'boolean'; - $length = 1; - break; - case 'text': - case 'varchar': - $fixed = false; - case 'unknown': - case 'char': - case 'bpchar': - $type[] = 'string'; - if ($length == '1') { - $type[] = 'boolean'; - if (preg_match('/^(is|has)/', $field['name'])) { - $type = array_reverse($type); - } - } elseif (strstr($db_type, 'text')) { - $type[] = 'clob'; - } - if ($fixed !== false) { - $fixed = true; - } - break; - case 'date': - $type[] = 'date'; - $length = null; - break; - case 'datetime': - case 'timestamp': - $type[] = 'timestamp'; - $length = null; - break; - case 'time': - $type[] = 'time'; - $length = null; - break; - case 'float': - case 'double': - case 'real': - $type[] = 'float'; - break; - case 'decimal': - case 'money': - case 'numeric': - $type[] = 'decimal'; - break; - case 'tinyblob': - case 'mediumblob': - case 'longblob': - case 'blob': - case 'bytea': - $type[] = 'blob'; - $length = null; - break; - case 'oid': - $type[] = 'blob'; - $type[] = 'clob'; - $length = null; - break; - case 'year': - $type[] = 'integer'; - $type[] = 'date'; - $length = null; - break; - default: - throw new Doctrine_DataDict_Exception('unknown database attribute type: '.$db_type); - } - - return array('type' => $type, - 'length' => $length, - 'unsigned' => $unsigned, - 'fixed' => $fixed); - } - /** - * Obtain DBMS specific SQL code portion needed to declare an integer type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * unsigned - * Boolean flag that indicates whether the field should be - * declared as unsigned integer if possible. - * - * default - * Integer value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - */ - public function getIntegerDeclaration($name, $field) - { - /** - if (!empty($field['unsigned'])) { - $this->conn->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; - } - */ - - if ( ! empty($field['autoincrement'])) { - $name = $this->conn->quoteIdentifier($name, true); - return $name . ' ' . $this->getNativeDeclaration($field); - } - - $default = ''; - if (array_key_exists('default', $field)) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) ? null : 0; - } - $default = ' DEFAULT '.$this->conn->quote($field['default'], $field['type']); - } - /** - TODO: is this needed ? - elseif (empty($field['notnull'])) { - $default = ' DEFAULT NULL'; - } - */ - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $name = $this->conn->quoteIdentifier($name, true); - return $name . ' ' . $this->getNativeDeclaration($field) . $default . $notnull; - } - /** - * parseBoolean - * parses a literal boolean value and returns - * proper sql equivalent - * - * @param string $value boolean value to be parsed - * @return string parsed boolean value - */ - public function parseBoolean($value) - { - return $value; - } -} +. + */ +Doctrine::autoload('Doctrine_DataDict'); +/** + * @package Doctrine + * @subpackage Doctrine_DataDict + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @author Konsta Vesterinen + * @author Paul Cooper + * @author Lukas Smith (PEAR MDB2 library) + * @version $Revision$ + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + */ +class Doctrine_DataDict_Pgsql extends Doctrine_DataDict +{ + /** + * @param array $reservedKeyWords an array of reserved keywords by pgsql + */ + protected static $reservedKeyWords = array( + 'abort', + 'absolute', + 'access', + 'action', + 'add', + 'after', + 'aggregate', + 'all', + 'alter', + 'analyse', + 'analyze', + 'and', + 'any', + 'as', + 'asc', + 'assertion', + 'assignment', + 'at', + 'authorization', + 'backward', + 'before', + 'begin', + 'between', + 'bigint', + 'binary', + 'bit', + 'boolean', + 'both', + 'by', + 'cache', + 'called', + 'cascade', + 'case', + 'cast', + 'chain', + 'char', + 'character', + 'characteristics', + 'check', + 'checkpoint', + 'class', + 'close', + 'cluster', + 'coalesce', + 'collate', + 'column', + 'comment', + 'commit', + 'committed', + 'constraint', + 'constraints', + 'conversion', + 'convert', + 'copy', + 'create', + 'createdb', + 'createuser', + 'cross', + 'current_date', + 'current_time', + 'current_timestamp', + 'current_user', + 'cursor', + 'cycle', + 'database', + 'day', + 'deallocate', + 'dec', + 'decimal', + 'declare', + 'default', + 'deferrable', + 'deferred', + 'definer', + 'delete', + 'delimiter', + 'delimiters', + 'desc', + 'distinct', + 'do', + 'domain', + 'double', + 'drop', + 'each', + 'else', + 'encoding', + 'encrypted', + 'end', + 'escape', + 'except', + 'exclusive', + 'execute', + 'exists', + 'explain', + 'external', + 'extract', + 'false', + 'fetch', + 'float', + 'for', + 'force', + 'foreign', + 'forward', + 'freeze', + 'from', + 'full', + 'function', + 'get', + 'global', + 'grant', + 'group', + 'handler', + 'having', + 'hour', + 'ilike', + 'immediate', + 'immutable', + 'implicit', + 'in', + 'increment', + 'index', + 'inherits', + 'initially', + 'inner', + 'inout', + 'input', + 'insensitive', + 'insert', + 'instead', + 'int', + 'integer', + 'intersect', + 'interval', + 'into', + 'invoker', + 'is', + 'isnull', + 'isolation', + 'join', + 'key', + 'lancompiler', + 'language', + 'leading', + 'left', + 'level', + 'like', + 'limit', + 'listen', + 'load', + 'local', + 'localtime', + 'localtimestamp', + 'location', + 'lock', + 'match', + 'maxvalue', + 'minute', + 'minvalue', + 'mode', + 'month', + 'move', + 'names', + 'national', + 'natural', + 'nchar', + 'new', + 'next', + 'no', + 'nocreatedb', + 'nocreateuser', + 'none', + 'not', + 'nothing', + 'notify', + 'notnull', + 'null', + 'nullif', + 'numeric', + 'of', + 'off', + 'offset', + 'oids', + 'old', + 'on', + 'only', + 'operator', + 'option', + 'or', + 'order', + 'out', + 'outer', + 'overlaps', + 'overlay', + 'owner', + 'partial', + 'password', + 'path', + 'pendant', + 'placing', + 'position', + 'precision', + 'prepare', + 'primary', + 'prior', + 'privileges', + 'procedural', + 'procedure', + 'read', + 'real', + 'recheck', + 'references', + 'reindex', + 'relative', + 'rename', + 'replace', + 'reset', + 'restrict', + 'returns', + 'revoke', + 'right', + 'rollback', + 'row', + 'rule', + 'schema', + 'scroll', + 'second', + 'security', + 'select', + 'sequence', + 'serializable', + 'session', + 'session_user', + 'set', + 'setof', + 'share', + 'show', + 'similar', + 'simple', + 'smallint', + 'some', + 'stable', + 'start', + 'statement', + 'statistics', + 'stdin', + 'stdout', + 'storage', + 'strict', + 'substring', + 'sysid', + 'table', + 'temp', + 'template', + 'temporary', + 'then', + 'time', + 'timestamp', + 'to', + 'toast', + 'trailing', + 'transaction', + 'treat', + 'trigger', + 'trim', + 'true', + 'truncate', + 'trusted', + 'type', + 'unencrypted', + 'union', + 'unique', + 'unknown', + 'unlisten', + 'until', + 'update', + 'usage', + 'user', + 'using', + 'vacuum', + 'valid', + 'validator', + 'values', + 'varchar', + 'varying', + 'verbose', + 'version', + 'view', + 'volatile', + 'when', + 'where', + 'with', + 'without', + 'work', + 'write', + 'year', + 'zone' + ); + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + */ + public function getNativeDeclaration(array $field) + { + if ( ! isset($field['type'])) { + throw new Doctrine_DataDict_Exception('Missing column type.'); + } + switch ($field['type']) { + case 'char': + case 'string': + case 'array': + case 'object': + case 'varchar': + case 'gzip': + $length = (isset($field['length']) && $field['length']) ? $field['length'] : null; + // TODO: $this->conn->options['default_text_field_length']; + + $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; + + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$this->conn->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + + case 'clob': + return 'TEXT'; + case 'blob': + return 'BYTEA'; + case 'enum': + case 'integer': + case 'int': + if (!empty($field['autoincrement'])) { + if (!empty($field['length'])) { + $length = $field['length']; + if ($length > 4) { + return 'BIGSERIAL'; + } + } + return 'SERIAL'; + } + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INT'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INT'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME without time zone'; + case 'timestamp': + return 'TIMESTAMP without time zone'; + case 'float': + case 'double': + return 'FLOAT8'; + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $this->conn->getAttribute(Doctrine::ATTR_DECIMAL_PLACES); + return 'NUMERIC('.$length.','.$scale.')'; + } + throw new Doctrine_DataDict_Exception('Unknown field type \'' . $field['type'] . '\'.'); + } + /** + * Maps a native array description of a field to a portable Doctrine datatype and length + * + * @param array $field native field description + * + * @return array containing the various possible types, length, sign, fixed + */ + public function getPortableDeclaration(array $field) + { + + $length = (isset($field['length'])) ? $field['length'] : null; + if ($length == '-1' && isset($field['atttypmod'])) { + $length = $field['atttypmod'] - 4; + } + if ((int)$length <= 0) { + $length = null; + } + $type = array(); + $unsigned = $fixed = null; + + if ( ! isset($field['name'])) { + $field['name'] = ''; + } + + $dbType = strtolower($field['type']); + + switch ($dbType) { + case 'smallint': + case 'int2': + $type[] = 'integer'; + $unsigned = false; + $length = 2; + if ($length == '2') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + break; + case 'int': + case 'int4': + case 'integer': + case 'serial': + case 'serial4': + $type[] = 'integer'; + $unsigned = false; + $length = 4; + break; + case 'bigint': + case 'int8': + case 'bigserial': + case 'serial8': + $type[] = 'integer'; + $unsigned = false; + $length = 8; + break; + case 'bool': + case 'boolean': + $type[] = 'boolean'; + $length = 1; + break; + case 'text': + case 'varchar': + $fixed = false; + case 'unknown': + case 'char': + case 'bpchar': + $type[] = 'string'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($dbType, 'text')) { + $type[] = 'clob'; + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type[] = 'float'; + break; + case 'decimal': + case 'money': + case 'numeric': + $type[] = 'decimal'; + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + case 'bytea': + $type[] = 'blob'; + $length = null; + break; + case 'oid': + $type[] = 'blob'; + $type[] = 'clob'; + $length = null; + break; + case 'year': + $type[] = 'integer'; + $type[] = 'date'; + $length = null; + break; + default: + throw new Doctrine_DataDict_Exception('unknown database attribute type: '.$dbType); + } + + return array('type' => $type, + 'length' => $length, + 'unsigned' => $unsigned, + 'fixed' => $fixed); + } + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field should be + * declared as unsigned integer if possible. + * + * default + * Integer value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + */ + public function getIntegerDeclaration($name, $field) + { + /** + if (!empty($field['unsigned'])) { + $this->conn->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + */ + + if ( ! empty($field['autoincrement'])) { + $name = $this->conn->quoteIdentifier($name, true); + return $name . ' ' . $this->getNativeDeclaration($field); + } + + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + $default = ' DEFAULT '.$this->conn->quote($field['default'], $field['type']); + } + /** + TODO: is this needed ? + elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + */ + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $this->conn->quoteIdentifier($name, true); + return $name . ' ' . $this->getNativeDeclaration($field) . $default . $notnull; + } + /** + * parseBoolean + * parses a literal boolean value and returns + * proper sql equivalent + * + * @param string $value boolean value to be parsed + * @return string parsed boolean value + */ + public function parseBoolean($value) + { + return $value; + } +} diff --git a/lib/Doctrine/Export.php b/lib/Doctrine/Export.php index 9962fb1a0..2b18c6a69 100644 --- a/lib/Doctrine/Export.php +++ b/lib/Doctrine/Export.php @@ -1,1093 +1,1094 @@ -. - */ -Doctrine::autoload('Doctrine_Connection_Module'); -/** - * Doctrine_Export - * - * @package Doctrine - * @author Konsta Vesterinen - * @author Lukas Smith (PEAR MDB2 library) - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @category Object Relational Mapping - * @link www.phpdoctrine.com - * @since 1.0 - * @version $Revision$ - */ -class Doctrine_Export extends Doctrine_Connection_Module -{ - protected $valid_default_values = array( - 'text' => '', - 'boolean' => true, - 'integer' => 0, - 'decimal' => 0.0, - 'float' => 0.0, - 'timestamp' => '1970-01-01 00:00:00', - 'time' => '00:00:00', - 'date' => '1970-01-01', - 'clob' => '', - 'blob' => '', - ); - - /** - * drop an existing database - * (this method is implemented by the drivers) - * - * @param string $name name of the database that should be dropped - * @return void - */ - public function dropDatabase($database) - { - $this->conn->execute($this->dropDatabaseSql($database)); - } - /** - * drop an existing database - * (this method is implemented by the drivers) - * - * @param string $name name of the database that should be dropped - * @return void - */ - public function dropDatabaseSql($database) - { - throw new Doctrine_Export_Exception('Drop database not supported by this driver.'); - } - /** - * dropTableSql - * drop an existing table - * - * @param string $table name of table that should be dropped from the database - * @return string - */ - public function dropTableSql($table) - { - return 'DROP TABLE ' . $this->conn->quoteIdentifier($table); - } - /** - * dropTable - * drop an existing table - * - * @param string $table name of table that should be dropped from the database - * @return void - */ - public function dropTable($table) - { - $this->conn->execute($this->dropTableSql($table)); - } - - /** - * drop existing index - * - * @param string $table name of table that should be used in method - * @param string $name name of the index to be dropped - * @return void - */ - public function dropIndex($table, $name) - { - return $this->conn->exec($this->dropIndexSql($table, $name)); - } - - /** - * dropIndexSql - * - * @param string $table name of table that should be used in method - * @param string $name name of the index to be dropped - * @return string SQL that is used for dropping an index - */ - public function dropIndexSql($table, $name) - { - $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name)); - return 'DROP INDEX ' . $name; - } - /** - * drop existing constraint - * - * @param string $table name of table that should be used in method - * @param string $name name of the constraint to be dropped - * @param string $primary hint if the constraint is primary - * @return void - */ - public function dropConstraint($table, $name, $primary = false) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name)); - return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name); - } - /** - * dropSequenceSql - * drop existing sequence - * (this method is implemented by the drivers) - * - * @throws Doctrine_Connection_Exception if something fails at database level - * @param string $sequenceName name of the sequence to be dropped - * @return void - */ - public function dropSequence($sequenceName) - { - $this->conn->exec($this->dropSequenceSql($sequenceName)); - } - /** - * dropSequenceSql - * drop existing sequence - * - * @throws Doctrine_Connection_Exception if something fails at database level - * @param string $sequenceName name of the sequence to be dropped - * @return void - */ - public function dropSequenceSql($sequenceName) - { - throw new Doctrine_Export_Exception('Drop sequence not supported by this driver.'); - } - /** - * create a new database - * (this method is implemented by the drivers) - * - * @param string $name name of the database that should be created - * @return void - */ - public function createDatabase($database) - { - $this->conn->execute($this->createDatabaseSql($database)); - } - /** - * create a new database - * (this method is implemented by the drivers) - * - * @param string $name name of the database that should be created - * @return string - */ - public function createDatabaseSql($database) - { - throw new Doctrine_Export_Exception('Create database not supported by this driver.'); - } - /** - * create a new table - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * The indexes of the array entries are the names of the fields of the table an - * the array entry values are associative arrays like those that are meant to be - * passed with the field definitions to get[Type]Declaration() functions. - * array( - * 'id' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * 'notnull' => 1 - * 'default' => 0 - * ), - * 'name' => array( - * 'type' => 'text', - * 'length' => 12 - * ), - * 'password' => array( - * 'type' => 'text', - * 'length' => 12 - * ) - * ); - * @param array $options An associative array of table options: - * - * @return string - */ - public function createTableSql($name, array $fields, array $options = array()) - { - if ( ! $name) { - throw new Doctrine_Export_Exception('no valid table name specified'); - } - - if (empty($fields)) { - throw new Doctrine_Export_Exception('no fields specified for table ' . $name); - } - - $queryFields = $this->getFieldDeclarationList($fields); - - - if (isset($options['primary']) && ! empty($options['primary'])) { - $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')'; - } - - if (isset($options['indexes']) && ! empty($options['indexes'])) { - foreach($options['indexes'] as $index => $definition) { - $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition); - } - } - - $name = $this->conn->quoteIdentifier($name, true); - $query = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; - - $sql[] = $query; - - if (isset($options['foreignKeys'])) { - - foreach ((array) $options['foreignKeys'] as $k => $definition) { - if (is_array($definition)) { - $sql[] = $this->createForeignKeySql($name, $definition); - } - } - } - return $sql; - } - /** - * create a new table - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * @param array $options An associative array of table options: - * @see Doctrine_Export::createTableSql() - * - * @return void - */ - public function createTable($name, array $fields, array $options = array()) - { - $sql = (array) $this->createTableSql($name, $fields, $options); - - foreach ($sql as $query) { - $this->conn->execute($query); - } - } - /** - * create sequence - * - * @throws Doctrine_Connection_Exception if something fails at database level - * @param string $seqName name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'charset' => 'utf8', - * 'collate' => 'utf8_unicode_ci', - * ); - * @return void - */ - public function createSequence($seqName, $start = 1, array $options = array()) - { - return $this->conn->execute($this->createSequenceSql($seqName, $start = 1, $options)); - } - /** - * return RDBMS specific create sequence statement - * (this method is implemented by the drivers) - * - * @throws Doctrine_Connection_Exception if something fails at database level - * @param string $seqName name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'charset' => 'utf8', - * 'collate' => 'utf8_unicode_ci', - * ); - * @return string - */ - public function createSequenceSql($seqName, $start = 1, array $options = array()) - { - throw new Doctrine_Export_Exception('Create sequence not supported by this driver.'); - } - /** - * create a constraint on a table - * - * @param string $table name of the table on which the constraint is to be created - * @param string $name name of the constraint to be created - * @param array $definition associative array that defines properties of the constraint to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the constraint fields as array - * constraints. Each entry of this array is set to another type of associative - * array that specifies properties of the constraint that are specific to - * each field. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array(), - * 'last_login' => array() - * ) - * ) - * @return void - */ - public function createConstraint($table, $name, $definition) - { - return $this->conn->exec($this->createConstraintSql($table, $name, $definition)); - } - /** - * create a constraint on a table - * - * @param string $table name of the table on which the constraint is to be created - * @param string $name name of the constraint to be created - * @param array $definition associative array that defines properties of the constraint to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the constraint fields as array - * constraints. Each entry of this array is set to another type of associative - * array that specifies properties of the constraint that are specific to - * each field. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array(), - * 'last_login' => array() - * ) - * ) - * @return void - */ - public function createConstraintSql($table, $name, $definition) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name)); - $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name; - - if (isset($definition['primary']) && $definition['primary']) { - $query .= ' PRIMARY KEY'; - } elseif (isset($definition['unique']) && $definition['unique']) { - $query .= ' UNIQUE'; - } - - $fields = array(); - foreach (array_keys($definition['fields']) as $field) { - $fields[] = $this->conn->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $fields) . ')'; - - return $query; - } - /** - * Get the stucture of a field into an array - * - * @param string $table name of the table on which the index is to be created - * @param string $name name of the index to be created - * @param array $definition associative array that defines properties of the index to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the index fields as array - * indexes. Each entry of this array is set to another type of associative - * array that specifies properties of the index that are specific to - * each field. - * - * Currently, only the sorting property is supported. It should be used - * to define the sorting direction of the index. It may be set to either - * ascending or descending. - * - * Not all DBMS support index sorting direction configuration. The DBMS - * drivers of those that do not support it ignore this property. Use the - * function supports() to determine whether the DBMS driver can manage indexes. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array( - * 'sorting' => 'ascending' - * ), - * 'last_login' => array() - * ) - * ) - * @return void - */ - public function createIndex($table, $name, array $definition) - { - return $this->conn->execute($this->createIndexSql($table, $name, $definition)); - } - /** - * Get the stucture of a field into an array - * - * @param string $table name of the table on which the index is to be created - * @param string $name name of the index to be created - * @param array $definition associative array that defines properties of the index to be created. - * @see Doctrine_Export::createIndex() - * @return string - */ - public function createIndexSql($table, $name, array $definition) - { - $table = $this->conn->quoteIdentifier($table); - $name = $this->conn->quoteIdentifier($name); - $type = ''; - - if(isset($definition['type'])) { - switch (strtolower($definition['type'])) { - case 'unique': - $type = strtoupper($definition['type']) . ' '; - break; - default: - throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']); - } - } - - $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; - - $fields = array(); - foreach (array_keys($definition['fields']) as $field) { - $fields[] = $this->conn->quoteIdentifier($field); - } - $query .= ' (' . implode(', ', $fields) . ')'; - - return $query; - } - /** - * createForeignKeySql - * - * @param string $table name of the table on which the foreign key is to be created - * @param array $definition associative array that defines properties of the foreign key to be created. - * @return string - */ - public function createForeignKeySql($table, array $definition) - { - $table = $this->conn->quoteIdentifier($table); - - $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $this->getForeignKeyDeclaration($definition); - - return $query; - } - /** - * alter an existing table - * (this method is implemented by the drivers) - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the MDB2 parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the MDB2 parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @return void - */ - public function alterTable($name, array $changes, $check) - { - $this->conn->execute($this->alterTableSql($name, $changes, $check)); - } - /** - * generates the sql for altering an existing table - * (this method is implemented by the drivers) - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @see Doctrine_Export::alterTable() - * @return string - */ - public function alterTableSql($name, array $changes, $check) - { - throw new Doctrine_Export_Exception('Alter table not supported by this driver.'); - } - /** - * Get declaration of a number of field in bulk - * - * @param array $fields a multidimensional associative array. - * The first dimension determines the field name, while the second - * dimension is keyed with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * charset - * Text value with the default CHARACTER SET for this field. - * collation - * Text value with the default COLLATION for this field. - * unique - * unique constraint - * - * @return string - */ - public function getFieldDeclarationList(array $fields) - { - foreach ($fields as $fieldName => $field) { - $query = $this->getDeclaration($fieldName, $field); - - $queryFields[] = $query; - } - return implode(', ', $queryFields); - } - /** - * Obtain DBMS specific SQL code portion needed to declare a generic type - * field to be used in statements like CREATE TABLE. - * - * @param string $name name the field to be declared. - * @param array $field associative array with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * length - * Integer value that determines the maximum length of the text - * field. If this argument is missing the field should be - * declared to have the longest length allowed by the DBMS. - * - * default - * Text value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * charset - * Text value with the default CHARACTER SET for this field. - * collation - * Text value with the default COLLATION for this field. - * unique - * unique constraint - * check - * column check constraint - * - * @return string DBMS specific SQL code portion that should be used to - * declare the specified field. - */ - public function getDeclaration($name, array $field) - { - - $default = $this->getDefaultFieldDeclaration($field); - - $charset = (isset($field['charset']) && $field['charset']) ? - ' ' . $this->getCharsetFieldDeclaration($field['charset']) : ''; - - $collation = (isset($field['collation']) && $field['collation']) ? - ' ' . $this->getCollationFieldDeclaration($field['collation']) : ''; - - $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; - - $unique = (isset($field['unique']) && $field['unique']) ? - ' ' . $this->getUniqueFieldDeclaration() : ''; - - $check = (isset($field['check']) && $field['check']) ? - ' ' . $field['check'] : ''; - - $method = 'get' . $field['type'] . 'Declaration'; - - if (method_exists($this->conn->dataDict, $method)) { - return $this->conn->dataDict->$method($name, $field); - } else { - $dec = $this->conn->dataDict->getNativeDeclaration($field); - } - return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $unique . $check . $collation; - } - /** - * getDefaultDeclaration - * Obtain DBMS specific SQL code portion needed to set a default value - * declaration to be used in statements like CREATE TABLE. - * - * @param array $field field definition array - * @return string DBMS specific SQL code portion needed to set a default value - */ - public function getDefaultFieldDeclaration($field) - { - $default = ''; - if (isset($field['default'])) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) - ? null : $this->valid_default_values[$field['type']]; - - if ($field['default'] === '' && - ($conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL)) { - $field['default'] = null; - } - } - - if ($field['type'] === 'boolean') { - $fields['default'] = $this->conn->convertBooleans($field['default']); - } - $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']); - } - return $default; - } - /** - * Obtain DBMS specific SQL code portion needed to set an index - * declaration to be used in statements like CREATE TABLE. - * - * @param string $charset name of the index - * @param array $definition index definition - * @return string DBMS specific SQL code portion needed to set an index - */ - public function getIndexDeclaration($name, array $definition) - { - $name = $this->conn->quoteIdentifier($name); - $type = ''; - - if (isset($definition['type'])) { - if (strtolower($definition['type']) == 'unique') { - $type = strtoupper($definition['type']) . ' '; - } else { - throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']); - } - } - - if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) { - throw new Doctrine_Export_Exception('No index columns given.'); - } - - $query = $type . 'INDEX ' . $name; - - $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; - - return $query; - } - /** - * getIndexFieldDeclarationList - * Obtain DBMS specific SQL code portion needed to set an index - * declaration to be used in statements like CREATE TABLE. - * - * @return string - */ - public function getIndexFieldDeclarationList(array $fields) - { - $ret = array(); - foreach ($fields as $field => $definition) { - if(is_array($definition)) { - $ret[] = $this->conn->quoteIdentifier($field); - } else { - $ret[] = $this->conn->quoteIdentifier($definition); - } - } - return implode(', ', $ret); - } - /** - * A method to return the required SQL string that fits between CREATE ... TABLE - * to create the table as a temporary table. - * - * Should be overridden in driver classes to return the correct string for the - * specific database type. - * - * The default is to return the string "TEMPORARY" - this will result in a - * SQL error for any database that does not support temporary tables, or that - * requires a different SQL command from "CREATE TEMPORARY TABLE". - * - * @return string The string required to be placed between "CREATE" and "TABLE" - * to generate a temporary table, if possible. - */ - public function getTemporaryTableQuery() - { - return 'TEMPORARY'; - } - /** - * getForeignKeyDeclaration - * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param array $definition an associative array with the following structure: - * name optional constraint name - * - * local the local field(s) - * - * foreign the foreign reference field(s) - * - * foreignTable the name of the foreign table - * - * onDelete referential delete action - * - * onUpdate referential update action - * - * deferred deferred constraint checking - * - * The onDelete and onUpdate keys accept the following values: - * - * CASCADE: Delete or update the row from the parent table and automatically delete or - * update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported. - * Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column - * in the parent table or in the child table. - * - * SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the - * child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier - * specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported. - * - * NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary - * key value is not allowed to proceed if there is a related foreign key value in the referenced table. - * - * RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as - * omitting the ON DELETE or ON UPDATE clause. - * - * SET DEFAULT - * - * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint - * of a field declaration. - */ - public function getForeignKeyDeclaration(array $definition) - { - $sql = $this->getForeignKeyBaseDeclaration($definition); - $sql .= $this->getAdvancedForeignKeyOptions($definition); - - return $sql; - } - /** - * getAdvancedForeignKeyOptions - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition foreign key definition - * @return string - */ - public function getAdvancedForeignKeyOptions(array $definition) - { - $query = ''; - if ( ! empty($definition['onUpdate'])) { - $query .= ' ON UPDATE ' . $this->getForeignKeyRefentialAction($definition['onUpdate']); - } - if ( ! empty($definition['onDelete'])) { - $query .= ' ON DELETE ' . $this->getForeignKeyRefentialAction($definition['onDelete']); - } - return $query; - } - /** - * getForeignKeyReferentialAction - * - * returns given referential action in uppercase if valid, otherwise throws - * an exception - * - * @throws Doctrine_Exception_Exception if unknown referential action given - * @param string $action foreign key referential action - * @param string foreign key referential action in uppercase - */ - public function getForeignKeyReferentialAction($action) - { - $upper = strtoupper($action); - switch ($upper) { - case 'CASCADE': - case 'SET NULL': - case 'NO ACTION': - case 'RESTRICT': - case 'SET DEFAULT': - return $upper; - break; - default: - throw new Doctrine_Export_Exception('Unknown foreign key referential action \'' . $upper . '\' given.'); - } - } - /** - * getForeignKeyBaseDeclaration - * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param array $definition - * @return string - */ - public function getForeignKeyBaseDeclaration(array $definition) - { - $sql = ''; - if (isset($definition['name'])) { - $sql .= 'CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' '; - } - $sql .= 'FOREIGN KEY ('; - - if ( ! isset($definition['local'])) { - throw new Doctrine_Export_Exception('Local reference field missing from definition.'); - } - if ( ! isset($definition['foreign'])) { - throw new Doctrine_Export_Exception('Foreign reference field missing from definition.'); - } - if ( ! isset($definition['foreignTable'])) { - throw new Doctrine_Export_Exception('Foreign reference table missing from definition.'); - } - - if ( ! is_array($definition['local'])) { - $definition['local'] = array($definition['local']); - } - if ( ! is_array($definition['foreign'])) { - $definition['foreign'] = array($definition['foreign']); - } - - $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local'])) - . ') REFERENCES ' - . $definition['foreignTable'] . '(' - . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')'; - - return $sql; - } - /** - * Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint - * of a field declaration to be used in statements like CREATE TABLE. - * - * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint - * of a field declaration. - */ - public function getUniqueFieldDeclaration() - { - return 'UNIQUE'; - } - /** - * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param string $charset name of the charset - * @return string DBMS specific SQL code portion needed to set the CHARACTER SET - * of a field declaration. - */ - public function getCharsetFieldDeclaration($charset) - { - return ''; - } - /** - * Obtain DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration to be used in statements like CREATE TABLE. - * - * @param string $collation name of the collation - * @return string DBMS specific SQL code portion needed to set the COLLATION - * of a field declaration. - */ - public function getCollationFieldDeclaration($collation) - { - return ''; - } - /** - * export - * method for exporting Doctrine_Record classes to a schema - * - * if the directory parameter is given this method first iterates - * recursively trhough the given directory in order to find any model classes - * - * Then it iterates through all declared classes and creates tables for the ones - * that extend Doctrine_Record and are not abstract classes - * - * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS - * occurred during the create table operation - * @param string $directory optional directory parameter - * @return void - */ - public function export($directory = null) - { - $sql = $this->exportSql($directory); - - $this->conn->beginTransaction(); - - foreach ($sql as $query) { - try { - $this->conn->exec($query); - } catch (Doctrine_Connection_Exception $e) { - // we only want to silence table already exists errors - if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { - $this->conn->rollback(); - throw $e; - } - } - } - $this->conn->commit(); - } - /** - * exportClasses - * method for exporting Doctrine_Record classes to a schema - * - * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS - * occurred during the create table operation - * @param array $classes - * @return void - */ - public function exportClasses(array $classes) - { - $sql = $this->exportClassesSql($classes); - - $this->conn->beginTransaction(); - - foreach ($sql as $query) { - try { - $this->conn->exec($query); - } catch (Doctrine_Connection_Exception $e) { - // we only want to silence table already exists errors - if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { - $this->conn->rollback(); - throw $e; - } - } - } - $this->conn->commit(); - } - /** - * exportClassesSql - * method for exporting Doctrine_Record classes to a schema - * - * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS - * occurred during the create table operation - * @param array $classes - * @return void - */ - public function exportClassesSql(array $classes) - { - $parent = new ReflectionClass('Doctrine_Record'); - - $sql = array(); - $fks = array(); - - // we iterate trhough the diff of previously declared classes - // and currently declared classes - foreach ($classes as $name) { - $class = new ReflectionClass($name); - $conn = Doctrine_Manager::getInstance()->getConnectionForComponent($name); - - // check if class is an instance of Doctrine_Record and not abstract - // class must have method setTableDefinition (to avoid non-Record subclasses like symfony's sfDoctrineRecord) - if ($class->isSubclassOf($parent) && ! $class->isAbstract() && method_exists($class->getName(), 'setTableDefinition')) { - $record = new $name(); - $table = $record->getTable(); - $data = $table->getExportableFormat(); - - $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']); - - if (is_array($query)) { - $sql = array_merge($sql, $query); - } else { - $sql[] = $query; - } - } - } - $sql = array_unique($sql); - rsort($sql); - - return $sql; - } - /** - * exportSql - * returns the sql for exporting Doctrine_Record classes to a schema - * - * if the directory parameter is given this method first iterates - * recursively trhough the given directory in order to find any model classes - * - * Then it iterates through all declared classes and creates tables for the ones - * that extend Doctrine_Record and are not abstract classes - * - * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS - * occurred during the create table operation - * @param string $directory optional directory parameter - * @return void - */ - public function exportSql($directory = null) - { - $declared = get_declared_classes(); - - if ($directory !== null) { - foreach ((array) $directory as $dir) { - $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), - RecursiveIteratorIterator::LEAVES_ONLY); - - foreach ($it as $file) { - $e = explode('.', $file->getFileName()); - if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) { - require_once $file->getPathName(); - } - } - } - $declared = array_diff(get_declared_classes(), $declared); - } - - return $this->exportClassesSql($declared); - } - /** - * exportTable - * exports given table into database based on column and option definitions - * - * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS - * occurred during the create table operation - * @return boolean whether or not the export operation was successful - * false if table already existed in the database - */ - public function exportTable(Doctrine_Table $table) - { - /** - TODO: maybe there should be portability option for the following check - if ( ! Doctrine::isValidClassname($table->getOption('declaringClass')->getName())) { - throw new Doctrine_Export_Exception('Class name not valid.'); - } - */ - - try { - $data = $table->getExportableFormat(); - - $this->conn->export->createTable($data['tableName'], $data['columns'], $data['options']); - } catch(Doctrine_Connection_Exception $e) { - // we only want to silence table already exists errors - if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { - throw $e; - } - } - } -} +. + */ +Doctrine::autoload('Doctrine_Connection_Module'); +/** + * Doctrine_Export + * + * @package Doctrine + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_Export extends Doctrine_Connection_Module +{ + protected $valid_default_values = array( + 'text' => '', + 'boolean' => true, + 'integer' => 0, + 'decimal' => 0.0, + 'float' => 0.0, + 'timestamp' => '1970-01-01 00:00:00', + 'time' => '00:00:00', + 'date' => '1970-01-01', + 'clob' => '', + 'blob' => '', + 'string' => '' + ); + + /** + * drop an existing database + * (this method is implemented by the drivers) + * + * @param string $name name of the database that should be dropped + * @return void + */ + public function dropDatabase($database) + { + $this->conn->execute($this->dropDatabaseSql($database)); + } + /** + * drop an existing database + * (this method is implemented by the drivers) + * + * @param string $name name of the database that should be dropped + * @return void + */ + public function dropDatabaseSql($database) + { + throw new Doctrine_Export_Exception('Drop database not supported by this driver.'); + } + /** + * dropTableSql + * drop an existing table + * + * @param string $table name of table that should be dropped from the database + * @return string + */ + public function dropTableSql($table) + { + return 'DROP TABLE ' . $this->conn->quoteIdentifier($table); + } + /** + * dropTable + * drop an existing table + * + * @param string $table name of table that should be dropped from the database + * @return void + */ + public function dropTable($table) + { + $this->conn->execute($this->dropTableSql($table)); + } + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return void + */ + public function dropIndex($table, $name) + { + return $this->conn->exec($this->dropIndexSql($table, $name)); + } + + /** + * dropIndexSql + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return string SQL that is used for dropping an index + */ + public function dropIndexSql($table, $name) + { + $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name)); + return 'DROP INDEX ' . $name; + } + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return void + */ + public function dropConstraint($table, $name, $primary = false) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name)); + return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name); + } + /** + * dropSequenceSql + * drop existing sequence + * (this method is implemented by the drivers) + * + * @throws Doctrine_Connection_Exception if something fails at database level + * @param string $sequenceName name of the sequence to be dropped + * @return void + */ + public function dropSequence($sequenceName) + { + $this->conn->exec($this->dropSequenceSql($sequenceName)); + } + /** + * dropSequenceSql + * drop existing sequence + * + * @throws Doctrine_Connection_Exception if something fails at database level + * @param string $sequenceName name of the sequence to be dropped + * @return void + */ + public function dropSequenceSql($sequenceName) + { + throw new Doctrine_Export_Exception('Drop sequence not supported by this driver.'); + } + /** + * create a new database + * (this method is implemented by the drivers) + * + * @param string $name name of the database that should be created + * @return void + */ + public function createDatabase($database) + { + $this->conn->execute($this->createDatabaseSql($database)); + } + /** + * create a new database + * (this method is implemented by the drivers) + * + * @param string $name name of the database that should be created + * @return string + */ + public function createDatabaseSql($database) + { + throw new Doctrine_Export_Exception('Create database not supported by this driver.'); + } + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * + * @return string + */ + public function createTableSql($name, array $fields, array $options = array()) + { + if ( ! $name) { + throw new Doctrine_Export_Exception('no valid table name specified'); + } + + if (empty($fields)) { + throw new Doctrine_Export_Exception('no fields specified for table ' . $name); + } + + $queryFields = $this->getFieldDeclarationList($fields); + + + if (isset($options['primary']) && ! empty($options['primary'])) { + $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')'; + } + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition); + } + } + + $name = $this->conn->quoteIdentifier($name, true); + $query = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; + + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + + foreach ((array) $options['foreignKeys'] as $k => $definition) { + if (is_array($definition)) { + $sql[] = $this->createForeignKeySql($name, $definition); + } + } + } + return $sql; + } + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options: + * @see Doctrine_Export::createTableSql() + * + * @return void + */ + public function createTable($name, array $fields, array $options = array()) + { + $sql = (array) $this->createTableSql($name, $fields, $options); + + foreach ($sql as $query) { + $this->conn->execute($query); + } + } + /** + * create sequence + * + * @throws Doctrine_Connection_Exception if something fails at database level + * @param string $seqName name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * ); + * @return void + */ + public function createSequence($seqName, $start = 1, array $options = array()) + { + return $this->conn->execute($this->createSequenceSql($seqName, $start = 1, $options)); + } + /** + * return RDBMS specific create sequence statement + * (this method is implemented by the drivers) + * + * @throws Doctrine_Connection_Exception if something fails at database level + * @param string $seqName name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * ); + * @return string + */ + public function createSequenceSql($seqName, $start = 1, array $options = array()) + { + throw new Doctrine_Export_Exception('Create sequence not supported by this driver.'); + } + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return void + */ + public function createConstraint($table, $name, $definition) + { + return $this->conn->exec($this->createConstraintSql($table, $name, $definition)); + } + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return void + */ + public function createConstraintSql($table, $name, $definition) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name)); + $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name; + + if (isset($definition['primary']) && $definition['primary']) { + $query .= ' PRIMARY KEY'; + } elseif (isset($definition['unique']) && $definition['unique']) { + $query .= ' UNIQUE'; + } + + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $this->conn->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + + return $query; + } + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return void + */ + public function createIndex($table, $name, array $definition) + { + return $this->conn->execute($this->createIndexSql($table, $name, $definition)); + } + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * @see Doctrine_Export::createIndex() + * @return string + */ + public function createIndexSql($table, $name, array $definition) + { + $table = $this->conn->quoteIdentifier($table); + $name = $this->conn->quoteIdentifier($name); + $type = ''; + + if(isset($definition['type'])) { + switch (strtolower($definition['type'])) { + case 'unique': + $type = strtoupper($definition['type']) . ' '; + break; + default: + throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']); + } + } + + $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table; + + $fields = array(); + foreach ($definition['fields'] as $field) { + $fields[] = $this->conn->quoteIdentifier($field); + } + $query .= ' (' . implode(', ', $fields) . ')'; + + return $query; + } + /** + * createForeignKeySql + * + * @param string $table name of the table on which the foreign key is to be created + * @param array $definition associative array that defines properties of the foreign key to be created. + * @return string + */ + public function createForeignKeySql($table, array $definition) + { + $table = $this->conn->quoteIdentifier($table); + + $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $this->getForeignKeyDeclaration($definition); + + return $query; + } + /** + * alter an existing table + * (this method is implemented by the drivers) + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @return void + */ + public function alterTable($name, array $changes, $check) + { + $this->conn->execute($this->alterTableSql($name, $changes, $check)); + } + /** + * generates the sql for altering an existing table + * (this method is implemented by the drivers) + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @see Doctrine_Export::alterTable() + * @return string + */ + public function alterTableSql($name, array $changes, $check) + { + throw new Doctrine_Export_Exception('Alter table not supported by this driver.'); + } + /** + * Get declaration of a number of field in bulk + * + * @param array $fields a multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * unique + * unique constraint + * + * @return string + */ + public function getFieldDeclarationList(array $fields) + { + foreach ($fields as $fieldName => $field) { + $query = $this->getDeclaration($fieldName, $field); + + $queryFields[] = $query; + } + return implode(', ', $queryFields); + } + /** + * Obtain DBMS specific SQL code portion needed to declare a generic type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * unique + * unique constraint + * check + * column check constraint + * + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + */ + public function getDeclaration($name, array $field) + { + + $default = $this->getDefaultFieldDeclaration($field); + + $charset = (isset($field['charset']) && $field['charset']) ? + ' ' . $this->getCharsetFieldDeclaration($field['charset']) : ''; + + $collation = (isset($field['collation']) && $field['collation']) ? + ' ' . $this->getCollationFieldDeclaration($field['collation']) : ''; + + $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; + + $unique = (isset($field['unique']) && $field['unique']) ? + ' ' . $this->getUniqueFieldDeclaration() : ''; + + $check = (isset($field['check']) && $field['check']) ? + ' ' . $field['check'] : ''; + + $method = 'get' . $field['type'] . 'Declaration'; + + if (method_exists($this->conn->dataDict, $method)) { + return $this->conn->dataDict->$method($name, $field); + } else { + $dec = $this->conn->dataDict->getNativeDeclaration($field); + } + return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $unique . $check . $collation; + } + /** + * getDefaultDeclaration + * Obtain DBMS specific SQL code portion needed to set a default value + * declaration to be used in statements like CREATE TABLE. + * + * @param array $field field definition array + * @return string DBMS specific SQL code portion needed to set a default value + */ + public function getDefaultFieldDeclaration($field) + { + $default = ''; + if (isset($field['default'])) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) + ? null : $this->valid_default_values[$field['type']]; + + if ($field['default'] === '' && + ($this->conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL)) { + $field['default'] = null; + } + } + + if ($field['type'] === 'boolean') { + $fields['default'] = $this->conn->convertBooleans($field['default']); + } + $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']); + } + return $default; + } + /** + * Obtain DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param string $charset name of the index + * @param array $definition index definition + * @return string DBMS specific SQL code portion needed to set an index + */ + public function getIndexDeclaration($name, array $definition) + { + $name = $this->conn->quoteIdentifier($name); + $type = ''; + + if (isset($definition['type'])) { + if (strtolower($definition['type']) == 'unique') { + $type = strtoupper($definition['type']) . ' '; + } else { + throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']); + } + } + + if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) { + throw new Doctrine_Export_Exception('No index columns given.'); + } + + $query = $type . 'INDEX ' . $name; + + $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; + + return $query; + } + /** + * getIndexFieldDeclarationList + * Obtain DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @return string + */ + public function getIndexFieldDeclarationList(array $fields) + { + $ret = array(); + foreach ($fields as $field => $definition) { + if(is_array($definition)) { + $ret[] = $this->conn->quoteIdentifier($field); + } else { + $ret[] = $this->conn->quoteIdentifier($definition); + } + } + return implode(', ', $ret); + } + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + public function getTemporaryTableQuery() + { + return 'TEMPORARY'; + } + /** + * getForeignKeyDeclaration + * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param array $definition an associative array with the following structure: + * name optional constraint name + * + * local the local field(s) + * + * foreign the foreign reference field(s) + * + * foreignTable the name of the foreign table + * + * onDelete referential delete action + * + * onUpdate referential update action + * + * deferred deferred constraint checking + * + * The onDelete and onUpdate keys accept the following values: + * + * CASCADE: Delete or update the row from the parent table and automatically delete or + * update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported. + * Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column + * in the parent table or in the child table. + * + * SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the + * child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier + * specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported. + * + * NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary + * key value is not allowed to proceed if there is a related foreign key value in the referenced table. + * + * RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as + * omitting the ON DELETE or ON UPDATE clause. + * + * SET DEFAULT + * + * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration. + */ + public function getForeignKeyDeclaration(array $definition) + { + $sql = $this->getForeignKeyBaseDeclaration($definition); + $sql .= $this->getAdvancedForeignKeyOptions($definition); + + return $sql; + } + /** + * getAdvancedForeignKeyOptions + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition foreign key definition + * @return string + */ + public function getAdvancedForeignKeyOptions(array $definition) + { + $query = ''; + if ( ! empty($definition['onUpdate'])) { + $query .= ' ON UPDATE ' . $this->getForeignKeyRefentialAction($definition['onUpdate']); + } + if ( ! empty($definition['onDelete'])) { + $query .= ' ON DELETE ' . $this->getForeignKeyRefentialAction($definition['onDelete']); + } + return $query; + } + /** + * getForeignKeyReferentialAction + * + * returns given referential action in uppercase if valid, otherwise throws + * an exception + * + * @throws Doctrine_Exception_Exception if unknown referential action given + * @param string $action foreign key referential action + * @param string foreign key referential action in uppercase + */ + public function getForeignKeyReferentialAction($action) + { + $upper = strtoupper($action); + switch ($upper) { + case 'CASCADE': + case 'SET NULL': + case 'NO ACTION': + case 'RESTRICT': + case 'SET DEFAULT': + return $upper; + break; + default: + throw new Doctrine_Export_Exception('Unknown foreign key referential action \'' . $upper . '\' given.'); + } + } + /** + * getForeignKeyBaseDeclaration + * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param array $definition + * @return string + */ + public function getForeignKeyBaseDeclaration(array $definition) + { + $sql = ''; + if (isset($definition['name'])) { + $sql .= 'CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' '; + } + $sql .= 'FOREIGN KEY ('; + + if ( ! isset($definition['local'])) { + throw new Doctrine_Export_Exception('Local reference field missing from definition.'); + } + if ( ! isset($definition['foreign'])) { + throw new Doctrine_Export_Exception('Foreign reference field missing from definition.'); + } + if ( ! isset($definition['foreignTable'])) { + throw new Doctrine_Export_Exception('Foreign reference table missing from definition.'); + } + + if ( ! is_array($definition['local'])) { + $definition['local'] = array($definition['local']); + } + if ( ! is_array($definition['foreign'])) { + $definition['foreign'] = array($definition['foreign']); + } + + $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local'])) + . ') REFERENCES ' + . $definition['foreignTable'] . '(' + . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')'; + + return $sql; + } + /** + * Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a field declaration. + */ + public function getUniqueFieldDeclaration() + { + return 'UNIQUE'; + } + /** + * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset name of the charset + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + public function getCharsetFieldDeclaration($charset) + { + return ''; + } + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation name of the collation + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + public function getCollationFieldDeclaration($collation) + { + return ''; + } + /** + * export + * method for exporting Doctrine_Record classes to a schema + * + * if the directory parameter is given this method first iterates + * recursively trhough the given directory in order to find any model classes + * + * Then it iterates through all declared classes and creates tables for the ones + * that extend Doctrine_Record and are not abstract classes + * + * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS + * occurred during the create table operation + * @param string $directory optional directory parameter + * @return void + */ + public function export($directory = null) + { + $sql = $this->exportSql($directory); + + $this->conn->beginTransaction(); + + foreach ($sql as $query) { + try { + $this->conn->exec($query); + } catch (Doctrine_Connection_Exception $e) { + // we only want to silence table already exists errors + if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { + $this->conn->rollback(); + throw $e; + } + } + } + $this->conn->commit(); + } + /** + * exportClasses + * method for exporting Doctrine_Record classes to a schema + * + * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS + * occurred during the create table operation + * @param array $classes + * @return void + */ + public function exportClasses(array $classes) + { + $sql = $this->exportClassesSql($classes); + + $this->conn->beginTransaction(); + + foreach ($sql as $query) { + try { + $this->conn->exec($query); + } catch (Doctrine_Connection_Exception $e) { + // we only want to silence table already exists errors + if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { + $this->conn->rollback(); + throw $e; + } + } + } + $this->conn->commit(); + } + /** + * exportClassesSql + * method for exporting Doctrine_Record classes to a schema + * + * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS + * occurred during the create table operation + * @param array $classes + * @return void + */ + public function exportClassesSql(array $classes) + { + $parent = new ReflectionClass('Doctrine_Record'); + + $sql = array(); + $fks = array(); + + // we iterate trhough the diff of previously declared classes + // and currently declared classes + foreach ($classes as $name) { + $class = new ReflectionClass($name); + $conn = Doctrine_Manager::getInstance()->getConnectionForComponent($name); + + // check if class is an instance of Doctrine_Record and not abstract + // class must have method setTableDefinition (to avoid non-Record subclasses like symfony's sfDoctrineRecord) + if ($class->isSubclassOf($parent) && ! $class->isAbstract() && method_exists($class->getName(), 'setTableDefinition')) { + $record = new $name(); + $table = $record->getTable(); + $data = $table->getExportableFormat(); + + $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']); + + if (is_array($query)) { + $sql = array_merge($sql, $query); + } else { + $sql[] = $query; + } + } + } + $sql = array_unique($sql); + rsort($sql); + + return $sql; + } + /** + * exportSql + * returns the sql for exporting Doctrine_Record classes to a schema + * + * if the directory parameter is given this method first iterates + * recursively trhough the given directory in order to find any model classes + * + * Then it iterates through all declared classes and creates tables for the ones + * that extend Doctrine_Record and are not abstract classes + * + * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS + * occurred during the create table operation + * @param string $directory optional directory parameter + * @return void + */ + public function exportSql($directory = null) + { + $declared = get_declared_classes(); + + if ($directory !== null) { + foreach ((array) $directory as $dir) { + $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir), + RecursiveIteratorIterator::LEAVES_ONLY); + + foreach ($it as $file) { + $e = explode('.', $file->getFileName()); + if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) { + require_once $file->getPathName(); + } + } + } + $declared = array_diff(get_declared_classes(), $declared); + } + + return $this->exportClassesSql($declared); + } + /** + * exportTable + * exports given table into database based on column and option definitions + * + * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS + * occurred during the create table operation + * @return boolean whether or not the export operation was successful + * false if table already existed in the database + */ + public function exportTable(Doctrine_Table $table) + { + /** + TODO: maybe there should be portability option for the following check + if ( ! Doctrine::isValidClassname($table->getOption('declaringClass')->getName())) { + throw new Doctrine_Export_Exception('Class name not valid.'); + } + */ + + try { + $data = $table->getExportableFormat(); + + $this->conn->export->createTable($data['tableName'], $data['columns'], $data['options']); + } catch(Doctrine_Connection_Exception $e) { + // we only want to silence table already exists errors + if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) { + throw $e; + } + } + } +} diff --git a/lib/Doctrine/Export/Pgsql.php b/lib/Doctrine/Export/Pgsql.php index 44f8fdd93..939a144b1 100644 --- a/lib/Doctrine/Export/Pgsql.php +++ b/lib/Doctrine/Export/Pgsql.php @@ -283,5 +283,68 @@ class Doctrine_Export_Pgsql extends Doctrine_Export return 'DROP SEQUENCE ' . $sequenceName; } + /** + * Creates a table. + * + * @param unknown_type $name + * @param array $fields + * @param array $options + * @return unknown + */ + public function createTableSql($name, array $fields, array $options = array()) + { + if ( ! $name) { + throw new Doctrine_Export_Exception('no valid table name specified'); + } + + if (empty($fields)) { + throw new Doctrine_Export_Exception('no fields specified for table ' . $name); + } + + $queryFields = $this->getFieldDeclarationList($fields); + + + if (isset($options['primary']) && ! empty($options['primary'])) { + $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')'; + } + + $name = $this->conn->quoteIdentifier($name, true); + $query = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; + + $sql[] = $query; + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $sql[] = $this->createIndexSql($name, $index, $definition); + } + } + + if (isset($options['foreignKeys'])) { + + foreach ((array) $options['foreignKeys'] as $k => $definition) { + if (is_array($definition)) { + $sql[] = $this->createForeignKeySql($name, $definition); + } + } + } + + return $sql; + } + + /** + * createForeignKeySql + * + * @param string $table name of the table on which the foreign key is to be created + * @param array $definition associative array that defines properties of the foreign key to be created. + * @return string + */ + public function createForeignKeySql($table, array $definition) + { + $table = $this->conn->quoteIdentifier($table); + + $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclaration($definition); + + return $query; + } } diff --git a/tests/Export/PgsqlTestCase.php b/tests/Export/PgsqlTestCase.php index 99255924a..09daec685 100644 --- a/tests/Export/PgsqlTestCase.php +++ b/tests/Export/PgsqlTestCase.php @@ -1,98 +1,98 @@ -. - */ - -/** - * Doctrine_Export_Mysql_TestCase - * - * @package Doctrine - * @author Konsta Vesterinen - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @category Object Relational Mapping - * @link www.phpdoctrine.com - * @since 1.0 - * @version $Revision$ - */ -class Doctrine_Export_Pgsql_TestCase extends Doctrine_UnitTestCase -{ - public function testCreateDatabaseExecutesSql() - { - $this->export->createDatabase('db'); - $this->assertEqual($this->adapter->pop(), 'CREATE DATABASE db'); - } - public function testDropDatabaseExecutesSql() - { - $this->export->dropDatabase('db'); - - $this->assertEqual($this->adapter->pop(), 'DROP DATABASE db'); - } - public function testCreateTableSupportsAutoincPks() - { - $name = 'mytable'; - - $fields = array('id' => array('type' => 'integer', 'unsigned' => 1, 'autoincrement' => true)); - $options = array('primary' => array('id')); - - $this->export->createTable($name, $fields, $options); - - $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id SERIAL, PRIMARY KEY(id))'); - } - public function testCreateTableSupportsDefaultAttribute() - { - $name = 'mytable'; - $fields = array('name' => array('type' => 'char', 'length' => 10, 'default' => 'def'), - 'type' => array('type' => 'integer', 'length' => 3, 'default' => 12) - ); - - $options = array('primary' => array('name', 'type')); - $this->export->createTable($name, $fields, $options); - - $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (name CHAR(10) DEFAULT \'def\', type INT DEFAULT 12, PRIMARY KEY(name, type))'); - } - public function testCreateTableSupportsMultiplePks() - { - $name = 'mytable'; - $fields = array('name' => array('type' => 'char', 'length' => 10), - 'type' => array('type' => 'integer', 'length' => 3)); - - $options = array('primary' => array('name', 'type')); - $this->export->createTable($name, $fields, $options); - - $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (name CHAR(10), type INT, PRIMARY KEY(name, type))'); - } - public function testExportSql() - { - $sql = $this->export->exportSql(dirname(__FILE__) . DIRECTORY_SEPARATOR . '_files'); - - $this->assertEqual($sql, array ( 0 => 'CREATE TABLE foo_reference (foo1 BIGINT, foo2 BIGINT, PRIMARY KEY(foo1, foo2))', - 1 => 'CREATE TABLE foo_locally_owned (id BIGSERIAL, name VARCHAR(200), PRIMARY KEY(id))', - 2 => 'CREATE TABLE foo_foreignly_owned_with_pk (id BIGSERIAL, name VARCHAR(200), PRIMARY KEY(id))', - 3 => 'CREATE TABLE foo_foreignly_owned (id BIGSERIAL, name VARCHAR(200), fooid BIGINT, PRIMARY KEY(id))', - 4 => 'CREATE TABLE foo_bar_record (fooid BIGINT, barid BIGINT, PRIMARY KEY(fooid, barid))', - 5 => 'CREATE TABLE foo (id BIGSERIAL, name VARCHAR(200) NOT NULL, parent_id BIGINT, local_foo BIGINT, PRIMARY KEY(id))', - 6 => 'CREATE TABLE bar (id BIGSERIAL, name VARCHAR(200), PRIMARY KEY(id))', - 7 => 'ALTER TABLE foo_reference ADD CONSTRAINT FOREIGN KEY (foo1) REFERENCES foo(foo1, foo2) NOT DEFERRABLE INITIALLY IMMEDIATE', - 8 => 'ALTER TABLE foo_bar_record ADD CONSTRAINT FOREIGN KEY (fooId) REFERENCES foo(fooid, barid) NOT DEFERRABLE INITIALLY IMMEDIATE', - 9 => 'ALTER TABLE foo ADD CONSTRAINT FOREIGN KEY (parent_id) REFERENCES foo(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE', - 10 => 'ALTER TABLE foo ADD CONSTRAINT FOREIGN KEY (local_foo) REFERENCES foo_locally_owned(id) ON DELETE RESTRICT NOT DEFERRABLE INITIALLY IMMEDIATE', - )); - } -} -?> +. + */ + +/** + * Doctrine_Export_Mysql_TestCase + * + * @package Doctrine + * @author Konsta Vesterinen + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision$ + */ +class Doctrine_Export_Pgsql_TestCase extends Doctrine_UnitTestCase +{ + public function testCreateDatabaseExecutesSql() + { + $this->export->createDatabase('db'); + $this->assertEqual($this->adapter->pop(), 'CREATE DATABASE db'); + } + public function testDropDatabaseExecutesSql() + { + $this->export->dropDatabase('db'); + + $this->assertEqual($this->adapter->pop(), 'DROP DATABASE db'); + } + public function testCreateTableSupportsAutoincPks() + { + $name = 'mytable'; + + $fields = array('id' => array('type' => 'integer', 'unsigned' => 1, 'autoincrement' => true)); + $options = array('primary' => array('id')); + + $this->export->createTable($name, $fields, $options); + + $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (id SERIAL, PRIMARY KEY(id))'); + } + public function testCreateTableSupportsDefaultAttribute() + { + $name = 'mytable'; + $fields = array('name' => array('type' => 'char', 'length' => 10, 'default' => 'def'), + 'type' => array('type' => 'integer', 'length' => 3, 'default' => 12) + ); + + $options = array('primary' => array('name', 'type')); + $this->export->createTable($name, $fields, $options); + + $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (name CHAR(10) DEFAULT \'def\', type INT DEFAULT 12, PRIMARY KEY(name, type))'); + } + public function testCreateTableSupportsMultiplePks() + { + $name = 'mytable'; + $fields = array('name' => array('type' => 'char', 'length' => 10), + 'type' => array('type' => 'integer', 'length' => 3)); + + $options = array('primary' => array('name', 'type')); + $this->export->createTable($name, $fields, $options); + + $this->assertEqual($this->adapter->pop(), 'CREATE TABLE mytable (name CHAR(10), type INT, PRIMARY KEY(name, type))'); + } + public function testExportSql() + { + $sql = $this->export->exportSql(dirname(__FILE__) . DIRECTORY_SEPARATOR . '_files'); + + $this->assertEqual($sql, array ( 0 => 'CREATE TABLE foo_reference (foo1 BIGINT, foo2 BIGINT, PRIMARY KEY(foo1, foo2))', + 1 => 'CREATE TABLE foo_locally_owned (id BIGSERIAL, name VARCHAR(200), PRIMARY KEY(id))', + 2 => 'CREATE TABLE foo_foreignly_owned_with_pk (id BIGSERIAL, name VARCHAR(200), PRIMARY KEY(id))', + 3 => 'CREATE TABLE foo_foreignly_owned (id BIGSERIAL, name VARCHAR(200), fooid BIGINT, PRIMARY KEY(id))', + 4 => 'CREATE TABLE foo_bar_record (fooid BIGINT, barid BIGINT, PRIMARY KEY(fooid, barid))', + 5 => 'CREATE TABLE foo (id BIGSERIAL, name VARCHAR(200) NOT NULL, parent_id BIGINT, local_foo BIGINT, PRIMARY KEY(id))', + 6 => 'CREATE TABLE bar (id BIGSERIAL, name VARCHAR(200), PRIMARY KEY(id))', + 7 => 'ALTER TABLE foo_reference ADD FOREIGN KEY (foo1) REFERENCES foo(id) NOT DEFERRABLE INITIALLY IMMEDIATE', + 8 => 'ALTER TABLE foo_bar_record ADD FOREIGN KEY (fooId) REFERENCES foo(id) NOT DEFERRABLE INITIALLY IMMEDIATE', + 9 => 'ALTER TABLE foo ADD FOREIGN KEY (parent_id) REFERENCES foo(id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE', + 10 => 'ALTER TABLE foo ADD FOREIGN KEY (local_foo) REFERENCES foo_locally_owned(id) ON DELETE RESTRICT NOT DEFERRABLE INITIALLY IMMEDIATE', + )); + } +} +?> diff --git a/tests/NestedSet/SingleRootTestCase.php b/tests/NestedSet/SingleRootTestCase.php index 3f74db71b..71f93026c 100644 --- a/tests/NestedSet/SingleRootTestCase.php +++ b/tests/NestedSet/SingleRootTestCase.php @@ -49,6 +49,14 @@ class Doctrine_NestedSet_SingleRoot_TestCase extends Doctrine_UnitTestCase $node2->name = 'node2'; $node2->getNode()->insertAsLastChildOf($node); } + + public function testLftRgtValues() + { + $treeMngr = $this->conn->getTable('NestedSetTest_SingleRootNode')->getTree(); + $root = $treeMngr->fetchRoot(); + $this->assertEqual(1, $root['lft']); + $this->assertEqual(4, $root['rgt']); + } public function testGetDescendants() { diff --git a/tests/run.php b/tests/run.php index 52dbeb8b0..dfebd5057 100644 --- a/tests/run.php +++ b/tests/run.php @@ -336,7 +336,7 @@ $test->addTestCase(new Doctrine_Search_TestCase()); //$test->addTestCase(new Doctrine_AuditLog_TestCase()); -//$test->addTestCase(new Doctrine_NestedSet_SingleRoot_TestCase()); +$test->addTestCase(new Doctrine_NestedSet_SingleRoot_TestCase()); // Cache tests //$test->addTestCase(new Doctrine_Cache_Query_SqliteTestCase());