diff --git a/lib/Doctrine/Association.php b/lib/Doctrine/Association.php index 4c55a853e..878c8cd16 100644 --- a/lib/Doctrine/Association.php +++ b/lib/Doctrine/Association.php @@ -103,13 +103,21 @@ class Doctrine_Association implements Serializable protected $_sourceFieldName; /** - * Identifies the field on the owning side that has the mapping for the + * Identifies the field on the owning side that controls the mapping for the * association. This is only set on the inverse side of an association. * * @var string */ protected $_mappedByFieldName; + /** + * Identifies the field on the inverse side of a bidirectional association. + * This is only set on the owning side of an association. + * + * @var string + */ + //protected $_inverseSideFieldName; + /** * The name of the join table, if any. * @@ -127,7 +135,16 @@ class Doctrine_Association implements Serializable */ public function __construct(array $mapping) { - /*$this->_mapping = array( + //$this->_initMappingArray(); + //$mapping = $this->_validateAndCompleteMapping($mapping); + //$this->_mapping = array_merge($this->_mapping, $mapping);*/ + + $this->_validateAndCompleteMapping($mapping); + } + + protected function _initMappingArray() + { + $this->_mapping = array( 'fieldName' => null, 'sourceEntity' => null, 'targetEntity' => null, @@ -137,10 +154,8 @@ class Doctrine_Association implements Serializable 'accessor' => null, 'mutator' => null, 'optional' => true, - 'cascade' => array() + 'cascades' => array() ); - $this->_mapping = array_merge($this->_mapping, $mapping);*/ - $this->_validateAndCompleteMapping($mapping); } /** @@ -342,6 +357,36 @@ class Doctrine_Association implements Serializable return $this->_mappedByFieldName; } + /*public function getInverseSideFieldName() + { + return $this->_inverseSideFieldName; + }*/ + /** + * Marks the association as bidirectional, specifying the field name of + * the inverse side. + * This is called on the owning side, when an inverse side is discovered. + * This does only make sense to call on the owning side. + * + * @param string $inverseSideFieldName + */ + /*public function setBidirectional($inverseSideFieldName) + { + if ( ! $this->_isOwningSide) { + return; //TODO: exception? + } + $this->_inverseSideFieldName = $inverseSideFieldName; + }*/ + + /** + * Whether the association is bidirectional. + * + * @return boolean + */ + /*public function isBidirectional() + { + return $this->_mappedByFieldName || $this->_inverseSideFieldName; + }*/ + /** * Whether the source field of the association has a custom accessor. * diff --git a/lib/Doctrine/Association/OneToOne.php b/lib/Doctrine/Association/OneToOne.php index f9751602e..69f576bb9 100644 --- a/lib/Doctrine/Association/OneToOne.php +++ b/lib/Doctrine/Association/OneToOne.php @@ -65,6 +65,12 @@ class Doctrine_Association_OneToOne extends Doctrine_Association parent::__construct($mapping); } + protected function _initMappingArray() + { + parent::_initMappingArray(); + $this->_mapping['deleteOrphans'] = false; + } + /** * Validates & completes the mapping. Mapping defaults are applied here. * diff --git a/lib/Doctrine/Builder/Record.php b/lib/Doctrine/Builder/Record.php index d075a44cc..d7d703fc1 100644 --- a/lib/Doctrine/Builder/Record.php +++ b/lib/Doctrine/Builder/Record.php @@ -666,7 +666,7 @@ END; */ public function buildRecord(array $definition) { - if ( !isset($definition['className'])) { + if ( ! isset($definition['className'])) { throw new Doctrine_Builder_Exception('Missing class name.'); } diff --git a/lib/Doctrine/ClassMetadata.php b/lib/Doctrine/ClassMetadata.php index d1b0d295d..4eb80bbb1 100644 --- a/lib/Doctrine/ClassMetadata.php +++ b/lib/Doctrine/ClassMetadata.php @@ -177,15 +177,16 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * - fieldName (string) * The name of the field in the Entity. * - * - type (string) - * The database type of the column. Can be one of Doctrine's portable types. + * - type (object Doctrine::DBAL::Types::* or custom type) + * The database type of the column. Can be one of Doctrine's portable types + * or a custom type. * * - columnName (string, optional) * The column name. Optional. Defaults to the field name. * * - length (integer, optional) - * The database length of the column. Optional. Defaults to Doctrine's - * default values for the portable types. + * The database length of the column. Optional. Default value taken from + * the type. * * - id (boolean, optional) * Marks the field as the primary key of the Entity. Multiple fields of an @@ -200,14 +201,26 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * - nullable (boolean, optional) * Whether the column is nullable. Defaults to TRUE. * - * - columnDefinition (string, optional) + * - columnDefinition (string, optional, schema-only) * The SQL fragment that is used when generating the DDL for the column. * - * - precision (integer, optional) + * - precision (integer, optional, schema-only) * The precision of a decimal column. Only valid if the column type is decimal. * - * - scale (integer, optional) + * - scale (integer, optional, schema-only) * The scale of a decimal column. Only valid if the column type is decimal. + * + * - index (string, optional, schema-only) + * Whether an index should be generated for the column. + * The value specifies the name of the index. To create a multi-column index, + * just use the same name for several mappings. + * + * - unique (string, optional, schema-only) + * Whether a unique constraint should be generated for the column. + * The value specifies the name of the unique constraint. To create a multi-column + * unique constraint, just use the same name for several mappings. + * + * - foreignKey (string, optional, schema-only) * * @var array */ @@ -275,25 +288,13 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable * * -- charset character set * - * -- checks the check constraints of this table, eg. 'price > dicounted_price' - * - * -- collation collation attribute - * - * -- indexes the index definitions of this table - * - * -- sequenceName Some databases need sequences instead of auto incrementation primary keys, - * you can set specific sequence for your table by calling setOption('sequenceName', $seqName) - * where $seqName is the name of the desired sequence + * -- collate collation attribute */ protected $_tableOptions = array( - 'tableName' => null, - 'sequenceName' => null, - 'type' => null, - 'charset' => null, - 'collation' => null, - 'collate' => null, - 'indexes' => array(), - 'checks' => array() + 'tableName' => null, + 'type' => null, + 'charset' => null, + 'collate' => null ); /** @@ -319,12 +320,19 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable protected $_lifecycleListeners = array(); /** - * The association mappings. + * The association mappings. All mappings, inverse and owning side. * * @var array */ protected $_associationMappings = array(); + /** + * List of inverse association mappings, indexed by mappedBy field name. + * + * @var array + */ + protected $_inverseMappings = array(); + /** * Flag indicating whether the identifier/primary key of the class is composite. * @@ -553,6 +561,32 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable return $this->_associationMappings[$fieldName]; } + /** + * Gets the inverse association mapping for the given fieldname. + * + * @param string $mappedByFieldName + * @return Doctrine::ORM::Mapping::AssociationMapping The mapping. + */ + public function getInverseAssociationMapping($mappedByFieldName) + { + if ( ! isset($this->_associationMappings[$fieldName])) { + throw Doctrine_MappingException::mappingNotFound($fieldName); + } + + return $this->_inverseMappings[$mappedByFieldName]; + } + + /** + * Whether the class has an inverse association mapping on the given fieldname. + * + * @param string $mappedByFieldName + * @return boolean + */ + public function hasInverseAssociationMapping($mappedByFieldName) + { + return isset($this->_inverseMappings[$mappedByFieldName]); + } + public function addAssociationMapping($fieldName, Doctrine_Association $assoc) { $this->_associationMappings[$fieldName] = $assoc; @@ -718,7 +752,6 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable if ( ! $this->_isIdentifierComposite && count($this->_identifier) > 1) { $this->_isIdentifierComposite = true; } - } return $mapping; @@ -1272,7 +1305,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } else if ($this->_inheritanceType == self::INHERITANCE_TYPE_JOINED) { // Remove inherited, non-pk fields. They're not in the table of this class foreach ($allColumns as $name => $definition) { - if (isset($definition['primary']) && $definition['primary'] === true) { + if (isset($definition['id']) && $definition['id'] === true) { if ($this->getParentClasses() && isset($definition['autoincrement'])) { unset($allColumns[$name]['autoincrement']); } @@ -1286,7 +1319,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable // If this is a subclass, just remove existing autoincrement options on the pk if ($this->getParentClasses()) { foreach ($allColumns as $name => $definition) { - if (isset($definition['primary']) && $definition['primary'] === true) { + if (isset($definition['id']) && $definition['id'] === true) { if (isset($definition['autoincrement'])) { unset($allColumns[$name]['autoincrement']); } @@ -1311,7 +1344,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } $columns[$name] = $definition; - if (isset($definition['primary']) && $definition['primary']) { + if (isset($definition['id']) && $definition['id']) { $primary[] = $name; } } @@ -1493,12 +1526,23 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable { $mapping = $this->_completeAssociationMapping($mapping); $oneToOneMapping = new Doctrine_Association_OneToOne($mapping); - $sourceFieldName = $oneToOneMapping->getSourceFieldName(); - if (isset($this->_associationMappings[$sourceFieldName])) { - throw Doctrine_MappingException::duplicateFieldMapping(); + $this->_storeAssociationMapping($oneToOneMapping); + + /*if ($oneToOneMapping->isInverseSide()) { + //FIXME: infinite recursion possible? + // Alternative: Store inverse side mappings indexed by mappedBy fieldname + // ($this->_inverseMappings). Then look it up. + $owningClass = $this->_em->getClassMetadata($oneToOneMapping->getTargetEntityName()); + $owningClass->getAssociationMapping($oneToOneMapping->getMappedByFieldName()) + ->setBidirectional($oneToOneMapping->getSourceFieldName()); + }*/ + } + + private function _registerMappingIfInverse(Doctrine_Association $assoc) + { + if ($assoc->isInverseSide()) { + $this->_inverseMappings[$assoc->getMappedByFieldName()] = $assoc; } - $this->_associationMappings[$sourceFieldName] = $oneToOneMapping; - $this->_registerCustomAssociationAccessors($oneToOneMapping, $sourceFieldName); } /** @@ -1510,12 +1554,7 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable { $mapping = $this->_completeAssociationMapping($mapping); $oneToManyMapping = new Doctrine_Association_OneToMany($mapping); - $sourceFieldName = $oneToManyMapping->getSourceFieldName(); - if (isset($this->_associationMappings[$sourceFieldName])) { - throw Doctrine_MappingException::duplicateFieldMapping(); - } - $this->_associationMappings[$sourceFieldName] = $oneToManyMapping; - $this->_registerCustomAssociationAccessors($oneToManyMapping, $sourceFieldName); + $this->_storeAssociationMapping($oneToManyMapping); } /** @@ -1530,11 +1569,31 @@ class Doctrine_ClassMetadata implements Doctrine_Configurable, Serializable } /** - * @todo Implementation. + * Adds a many-to-many mapping. + * + * @param array $mapping The mapping. */ public function mapManyToMany(array $mapping) { - + $mapping = $this->_completeAssociationMapping($mapping); + $manyToManyMapping = new Doctrine_Association_ManyToManyMapping($mapping); + $this->_storeAssociationMapping($manyToManyMapping); + } + + /** + * Stores the association mapping. + * + * @param Doctrine_Association $assocMapping + */ + private function _storeAssociationMapping(Doctrine_Association $assocMapping) + { + $sourceFieldName = $assocMapping->getSourceFieldName(); + if (isset($this->_associationMappings[$sourceFieldName])) { + throw Doctrine_MappingException::duplicateFieldMapping(); + } + $this->_associationMappings[$sourceFieldName] = $assocMapping; + $this->_registerCustomAssociationAccessors($assocMapping, $sourceFieldName); + $this->_registerMappingIfInverse($assocMapping); } /** diff --git a/lib/Doctrine/Collection.php b/lib/Doctrine/Collection.php index 60edbfe4d..8e871e72b 100644 --- a/lib/Doctrine/Collection.php +++ b/lib/Doctrine/Collection.php @@ -31,9 +31,9 @@ * * A collection of entities represents only the associations (links) to those entities. * That means, if the collection is part of a many-many mapping and you remove - * entities from the collection, only the links in the xref table are removed. + * entities from the collection, only the links in the xref table are removed (on flush). * Similarly, if you remove entities from a collection that is part of a one-many - * mapping this will only result in the nulling out of the foreign keys + * mapping this will only result in the nulling out of the foreign keys on flush * (or removal of the links in the xref table if the one-many is mapped through an * xref table). If you want entities in a one-many collection to be removed when * they're removed from the collection, use deleteOrphans => true on the one-many @@ -44,6 +44,7 @@ * @version $Revision$ * @author Konsta Vesterinen * @author Roman Borschel + * @todo Add more typical Collection methods. */ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, ArrayAccess { @@ -86,7 +87,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, protected $_association; /** - * The name of the column that is used for collection key mapping. + * The name of the field that is used for collection key mapping. * * @var string */ @@ -100,14 +101,31 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, //protected static $null; /** - * The EntityManager. + * The EntityManager that manages the persistence of the collection. * - * @var EntityManager + * @var Doctrine::ORM::EntityManager */ protected $_em; + + /** + * The name of the field on the target entities that points to the owner + * of the collection. This is only set if the association is bidirectional. + * + * @var string + */ + protected $_backRefFieldName; + + /** + * Hydration flag. + * + * @var boolean + * @see _setHydrationFlag() + */ + protected $_hydrationFlag; /** * Constructor. + * Creates a new persistent collection. */ public function __construct($entityBaseType, $keyField = null) { @@ -126,6 +144,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * setData * * @param array $data + * @todo Remove? */ public function setData(array $data) { @@ -133,56 +152,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, } /** - * Serializes the collection. - * This method is automatically called when the Collection is serialized. - * - * Part of the implementation of the Serializable interface. - * - * @return array - */ - public function serialize() - { - $vars = get_object_vars($this); - - unset($vars['reference']); - unset($vars['relation']); - unset($vars['expandable']); - unset($vars['expanded']); - unset($vars['generator']); - - return serialize($vars); - } - - /** - * Reconstitutes the collection object from it's serialized form. - * This method is automatically called everytime the Collection object is unserialized. - * - * Part of the implementation of the Serializable interface. - * - * @param string $serialized The serialized data - * - * @return void - */ - public function unserialize($serialized) - { - $manager = Doctrine_EntityManagerFactory::getManager(); - $connection = $manager->getConnection(); - - $array = unserialize($serialized); - - foreach ($array as $name => $values) { - $this->$name = $values; - } - - $keyColumn = isset($array['keyField']) ? $array['keyField'] : null; - - if ($keyColumn !== null) { - $this->_keyField = $keyColumn; - } - } - - /** - * Sets the key column for this collection + * INTERNAL: Sets the key column for this collection * * @param string $column * @return Doctrine_Collection @@ -194,7 +164,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, } /** - * returns the name of the key column + * INTERNAL: returns the name of the key column * * @return string */ @@ -208,7 +178,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * * @return array */ - public function getData() + public function unwrap() { return $this->_data; } @@ -255,29 +225,25 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, /** * INTERNAL: - * sets a reference pointer + * Sets the collection owner. Used (only?) during hydration. * * @return void */ - public function setReference(Doctrine_Entity $entity, Doctrine_Association $relation) + public function _setOwner(Doctrine_Entity $entity, Doctrine_Association $relation) { $this->_owner = $entity; $this->_association = $relation; - - /*if ($relation instanceof Doctrine_Relation_ForeignKey || - $relation instanceof Doctrine_Relation_LocalKey) { - $this->referenceField = $relation->getForeignFieldName(); - $value = $entity->get($relation->getLocalFieldName()); - foreach ($this->_data as $entity) { - if ($value !== null) { - $entity->set($this->referenceField, $value, false); - } else { - $entity->set($this->referenceField, $this->_owner, false); - } + if ($relation->isInverseSide()) { + // for sure bidirectional + $this->_backRefFieldName = $relation->getMappedByFieldName(); + } else { + $targetClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); + if ($targetClass->hasInverseAssociationMapping($relation->getSourceFieldName())) { + // bidirectional + $this->_backRefFieldName = $targetClass->getInverseAssociationMapping( + $relation->getSourceFieldName())->getSourceFieldName(); } - } else if ($relation instanceof Doctrine_Relation_Association) { - - }*/ + } } /** @@ -286,7 +252,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * * @return mixed */ - public function getReference() + public function _getOwner() { return $this->_owner; } @@ -508,8 +474,9 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, */ public function add($value, $key = null) { + //TODO: really only allow entities? if ( ! $value instanceof Doctrine_Entity) { - throw new Doctrine_Record_Exception('Value variable in set is not an instance of Doctrine_Entity.'); + throw new Doctrine_Record_Exception('Value variable in collection is not an instance of Doctrine_Entity.'); } // TODO: Really prohibit duplicates? @@ -526,8 +493,15 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, $this->_data[] = $value; } - //TODO: Register collection as dirty with the UoW if necessary - $this->_changed(); + if ($this->_hydrationFlag) { + if ($this->_backRefFieldName) { + // set back reference to owner + $value->_internalSetReference($this->_backRefFieldName, $this->_owner); + } + } else { + //TODO: Register collection as dirty with the UoW if necessary + $this->_changed(); + } return true; } @@ -553,7 +527,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * @return boolean * @todo New implementation & maybe move elsewhere. */ - public function loadRelated($name = null) + /*public function loadRelated($name = null) { $list = array(); $query = new Doctrine_Query($this->_mapper->getConnection()); @@ -595,7 +569,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, $coll = $query->query($dql, $list); $this->populateRelated($name, $coll); - } + }*/ /** * INTERNAL: @@ -606,7 +580,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * @return void * @todo New implementation & maybe move elsewhere. */ - protected function populateRelated($name, Doctrine_Collection $coll) + /*protected function populateRelated($name, Doctrine_Collection $coll) { $rel = $this->_mapper->getTable()->getRelation($name); $table = $rel->getTable(); @@ -658,6 +632,23 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, } } + }*/ + + /** + * INTERNAL: + * Sets a flag that indicates whether the collection is currently being hydrated. + * This has the following consequences: + * 1) During hydration, bidirectional associations are completed automatically + * by setting the back reference. + * 2) During hydration no change notifications are reported to the UnitOfWork. + * I.e. that means add() etc. do not cause the collection to be scheduled + * for an update. + * + * @param boolean $bool + */ + public function _setHydrationFlag($bool) + { + $this->_hydrationFlag = $bool; } /** @@ -671,7 +662,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * * @return void */ - public function takeSnapshot() + public function _takeSnapshot() { $this->_snapshot = $this->_data; } @@ -681,13 +672,13 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * * @return array returns the data in last snapshot */ - public function getSnapshot() + public function _getSnapshot() { return $this->_snapshot; } /** - * Processes the difference of the last snapshot and the current data. + * INTERNAL: Processes the difference of the last snapshot and the current data. * * an example: * Snapshot with the objects 1, 2 and 4 @@ -696,6 +687,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, * The process would remove objects 1 and 4 * * @return Doctrine_Collection + * @todo Move elsewhere */ public function processDiff() { @@ -706,9 +698,10 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, } /** - * Mimics the result of a $query->execute(array(), Doctrine::FETCH_ARRAY); + * Creates an array representation of the collection. * * @param boolean $deep + * @return array */ public function toArray($deep = false, $prefixKey = false) { @@ -836,7 +829,6 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, if ($a->getOid() == $b->getOid()) { return 0; } - return ($a->getOid() > $b->getOid()) ? 1 : -1; } @@ -916,6 +908,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, /** * getIterator + * * @return object ArrayIterator */ public function getIterator() @@ -933,7 +926,7 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, } /** - * Gets the association mapping of the collection. + * INTERNAL: Gets the association mapping of the collection. * * @return Doctrine::ORM::Mapping::AssociationMapping */ @@ -989,4 +982,55 @@ class Doctrine_Collection implements Countable, IteratorAggregate, Serializable, $this->_em->getUnitOfWork()->scheduleCollectionUpdate($this); }*/ } + + /* Serializable implementation */ + + /** + * Serializes the collection. + * This method is automatically called when the Collection is serialized. + * + * Part of the implementation of the Serializable interface. + * + * @return array + */ + public function serialize() + { + $vars = get_object_vars($this); + + unset($vars['reference']); + unset($vars['relation']); + unset($vars['expandable']); + unset($vars['expanded']); + unset($vars['generator']); + + return serialize($vars); + } + + /** + * Reconstitutes the collection object from it's serialized form. + * This method is automatically called everytime the Collection object is unserialized. + * + * Part of the implementation of the Serializable interface. + * + * @param string $serialized The serialized data + * + * @return void + */ + public function unserialize($serialized) + { + $manager = Doctrine_EntityManagerFactory::getManager(); + $connection = $manager->getConnection(); + + $array = unserialize($serialized); + + foreach ($array as $name => $values) { + $this->$name = $values; + } + + $keyColumn = isset($array['keyField']) ? $array['keyField'] : null; + + if ($keyColumn !== null) { + $this->_keyField = $keyColumn; + } + } } diff --git a/lib/Doctrine/Configuration.php b/lib/Doctrine/Configuration.php index cfc897fdc..0ec57c482 100644 --- a/lib/Doctrine/Configuration.php +++ b/lib/Doctrine/Configuration.php @@ -26,6 +26,9 @@ /** * The Configuration is the container for all configuration options of Doctrine. * It combines all configuration options from DBAL & ORM. + * + * INTERNAL: When adding a new configuration option just write a getter/setter + * combination and add the option to the _attributes array with a proper default value. * * @author Roman Borschel * @since 2.0 @@ -36,6 +39,8 @@ class Doctrine_Configuration /** * The attributes that are contained in the configuration. + * Values are default values. PHP null is replaced by a reference to the Null + * object on instantiation in order to use isset() instead of array_key_exists(). * * @var array */ @@ -44,12 +49,9 @@ class Doctrine_Configuration 'indexNameFormat' => '%s_idx', 'sequenceNameFormat' => '%s_seq', 'tableNameFormat' => '%s', - 'resultCache' => null, - 'resultCacheLifeSpan' => null, - 'queryCache' => null, - 'queryCacheLifeSpan' => null, - 'metadataCache' => null, - 'metadataCacheLifeSpan' => null + 'resultCacheImpl' => null, + 'queryCacheImpl' => null, + 'metadataCacheImpl' => null, ); /** @@ -77,38 +79,6 @@ class Doctrine_Configuration } } - /** - * Gets the value of a configuration attribute. - * - * @param string $name - * @return mixed - */ - public function get($name) - { - if ( ! $this->has($name)) { - throw Doctrine_Configuration_Exception::unknownAttribute($name); - } - if ($this->_attributes[$name] === $this->_nullObject) { - return null; - } - return $this->_attributes[$name]; - } - - /** - * Sets the value of a configuration attribute. - * - * @param string $name - * @param mixed $value - */ - public function set($name, $value) - { - if ( ! $this->has($name)) { - throw Doctrine_Configuration_Exception::unknownAttribute($name); - } - // TODO: do some value checking depending on the attribute - $this->_attributes[$name] = $value; - } - /** * Checks whether the configuration contains/supports an attribute. * @@ -119,4 +89,91 @@ class Doctrine_Configuration { return isset($this->_attributes[$name]); } + + public function getQuoteIdentifiers() + { + return $this->_attributes['quoteIdentifiers']; + } + + public function setQuoteIdentifiers($bool) + { + $this->_attributes['quoteIdentifiers'] = (bool)$bool; + } + + public function getIndexNameFormat() + { + return $this->_attributes['indexNameFormat']; + } + + public function setIndexNameFormat($format) + { + //TODO: check format? + $this->_attributes['indexNameFormat'] = $format; + } + + public function getSequenceNameFormat() + { + return $this->_attributes['sequenceNameFormat']; + } + + public function setSequenceNameFormat($format) + { + //TODO: check format? + $this->_attributes['sequenceNameFormat'] = $format; + } + + public function getTableNameFormat() + { + return $this->_attributes['tableNameFormat']; + } + + public function setTableNameFormat($format) + { + //TODO: check format? + $this->_attributes['tableNameFormat'] = $format; + } + + public function getResultCacheImpl() + { + return $this->_attributes['resultCacheImpl']; + } + + public function setResultCacheImpl(Doctrine_Cache_Interface $cacheImpl) + { + $this->_attributes['resultCacheImpl'] = $cacheImpl; + } + + public function getQueryCacheImpl() + { + return $this->_attributes['queryCacheImpl']; + } + + public function setQueryCacheImpl(Doctrine_Cache_Interface $cacheImpl) + { + $this->_attributes['queryCacheImpl'] = $cacheImpl; + } + + public function getMetadataCacheImpl() + { + return $this->_attributes['metadataCacheImpl']; + } + + public function setMetadataCacheImpl(Doctrine_Cache_Interface $cacheImpl) + { + $this->_attributes['metadataCacheImpl'] = $cacheImpl; + } + + public function setCustomTypes(array $types) + { + foreach ($types as $name => $typeClassName) { + Doctrine_DataType::addCustomType($name, $typeClassName); + } + } + + public function setTypeOverrides(array $overrides) + { + foreach ($override as $name => $typeClassName) { + Doctrine_DataType::overrideType($name, $typeClassName); + } + } } \ No newline at end of file diff --git a/lib/Doctrine/Connection.php b/lib/Doctrine/Connection.php index 06a416416..5a4e57f53 100644 --- a/lib/Doctrine/Connection.php +++ b/lib/Doctrine/Connection.php @@ -180,13 +180,26 @@ abstract class Doctrine_Connection * * @param array $params The connection parameters. */ - public function __construct(array $params) + public function __construct(array $params, $config = null, $eventManager = null) { if (isset($params['pdo'])) { $this->_pdo = $params['pdo']; $this->_isConnected = true; } $this->_params = $params; + + // Create default config and event manager if none given + if ( ! $config) { + $this->_config = new Doctrine_Configuration(); + } + if ( ! $eventManager) { + $this->_eventManager = new Doctrine_EventManager(); + } + + // create platform + $class = "Doctrine_DatabasePlatform_" . $this->_driverName . "Platform"; + $this->_platform = new $class(); + $this->_platform->setQuoteIdentifiers($this->_config->getQuoteIdentifiers()); } /** @@ -242,8 +255,7 @@ abstract class Doctrine_Connection */ public function getDatabasePlatform() { - throw new Doctrine_Connection_Exception("No DatabasePlatform available " - . "for connection " . get_class($this)); + return $this->_platform; } /** @@ -558,10 +570,12 @@ abstract class Doctrine_Connection * @param bool $checkOption check the 'quote_identifier' option * * @return string quoted identifier string + * @todo Moved to DatabasePlatform + * @deprecated */ - public function quoteIdentifier($str, $checkOption = true) + public function quoteIdentifier($str) { - if (is_null($this->_quoteIdentifiers)) { + /*if (is_null($this->_quoteIdentifiers)) { $this->_quoteIdentifiers = $this->_config->get('quoteIdentifiers'); } if ( ! $this->_quoteIdentifiers) { @@ -579,7 +593,8 @@ abstract class Doctrine_Connection $c = $this->_platform->getIdentifierQuoteCharacter(); $str = str_replace($c, $c . $c, $str); - return $c . $str . $c; + return $c . $str . $c;*/ + return $this->getDatabasePlatform()->quoteIdentifier($str); } /** @@ -590,6 +605,7 @@ abstract class Doctrine_Connection * * @param array $item * @return void + * @deprecated Moved to DatabasePlatform */ public function convertBooleans($item) { diff --git a/lib/Doctrine/Connection/Mysql.php b/lib/Doctrine/Connection/Mysql.php index df7e7b83c..5161b220a 100644 --- a/lib/Doctrine/Connection/Mysql.php +++ b/lib/Doctrine/Connection/Mysql.php @@ -190,18 +190,5 @@ class Doctrine_Connection_Mysql extends Doctrine_Connection_Common } return $dsn; - } - - /** - * Gets the DatabasePlatform for the connection. - * - * @return Doctrine::DBAL::Platforms::MySqlPlatform - */ - public function getDatabasePlatform() - { - if ( ! $this->_platform) { - $this->_platform = new Doctrine_DatabasePlatform_MySqlPlatform(); - } - return $this->_platform; } } diff --git a/lib/Doctrine/Connection/Pgsql.php b/lib/Doctrine/Connection/Pgsql.php index 481a370b0..566923b4b 100644 --- a/lib/Doctrine/Connection/Pgsql.php +++ b/lib/Doctrine/Connection/Pgsql.php @@ -70,7 +70,8 @@ class Doctrine_Connection_Pgsql extends Doctrine_Connection_Common * This method takes care of that conversion * * @param array $item - * @return void + * @return void + * @deprecated Moved to PostgreSqlPlatform */ public function convertBooleans($item) { @@ -94,7 +95,7 @@ class Doctrine_Connection_Pgsql extends Doctrine_Connection_Common * @param string $native determines if the raw version string should be returned * @return array|string an array or string with version information */ - public function getServerVersion($native = false) + /*public function getServerVersion($native = false) { $query = 'SHOW SERVER_VERSION'; @@ -124,5 +125,5 @@ class Doctrine_Connection_Pgsql extends Doctrine_Connection_Common } } return $serverInfo; - } + }*/ } \ No newline at end of file diff --git a/lib/Doctrine/ConnectionFactory.php b/lib/Doctrine/ConnectionFactory.php index 8f61cc243..56c467e05 100644 --- a/lib/Doctrine/ConnectionFactory.php +++ b/lib/Doctrine/ConnectionFactory.php @@ -46,12 +46,46 @@ class Doctrine_ConnectionFactory 'firebird' => 'Doctrine_Connection_Firebird', 'informix' => 'Doctrine_Connection_Informix', 'mock' => 'Doctrine_Connection_Mock'); + + /** + * The EventManager that is injected into all created Connections. + * + * @var EventManager + */ + private $_eventManager; + + /** + * The Configuration that is injected into all created Connections. + * + * @var Configuration + */ + private $_config; public function __construct() { } + /** + * Sets the Configuration that is injected into all Connections. + * + * @param Doctrine_Configuration $config + */ + public function setConfiguration(Doctrine_Configuration $config) + { + $this->_config = $config; + } + + /** + * Sets the EventManager that is injected into all Connections. + * + * @param Doctrine_EventManager $eventManager + */ + public function setEventManager(Doctrine_EventManager $eventManager) + { + $this->_eventManager = $eventManager; + } + /** * Creates a connection object with the specified parameters. * @@ -60,6 +94,14 @@ class Doctrine_ConnectionFactory */ public function createConnection(array $params) { + // create default config and event manager, if not set + if ( ! $this->_config) { + $this->_config = new Doctrine_Configuration(); + } + if ( ! $this->_eventManager) { + $this->_eventManager = new Doctrine_EventManager(); + } + // check for existing pdo object if (isset($params['pdo']) && ! $params['pdo'] instanceof PDO) { throw Doctrine_ConnectionFactory_Exception::invalidPDOInstance(); @@ -70,7 +112,7 @@ class Doctrine_ConnectionFactory } $className = $this->_drivers[$params['driver']]; - return new $className($params); + return new $className($params, $this->_config, $this->_eventManager); } /** diff --git a/lib/Doctrine/DataType.php b/lib/Doctrine/DataType.php new file mode 100644 index 000000000..471165f5e --- /dev/null +++ b/lib/Doctrine/DataType.php @@ -0,0 +1,78 @@ + 'Doctrine_DataType_IntegerType', + 'string' => 'Doctrine_DataType_StringType', + 'text' => 'Doctrine_DataType_TextType', + 'datetime' => 'Doctrine_DataType_DateTimeType', + 'decimal' => 'Doctrine_DataType_DecimalType', + 'double' => 'Doctrine_DataType_DoubleType' + ); + + public function convertToDatabaseValue($value, Doctrine_DatabasePlatform $platform) + { + return $value; + } + + public function convertToObjectValue($value) + { + return $value; + } + + abstract public function getDefaultLength(Doctrine_DatabasePlatform $platform); + abstract public function getSqlDeclaration(array $fieldDeclaration, Doctrine_DatabasePlatform $platform); + abstract public function getName(); + + /** + * Factory method. + * + * @param string $name The name of the type (as returned by getName()). + * @return Doctrine::DBAL::Types::Type + */ + public static function getType($name) + { + if ( ! isset(self::$_typeObjects[$name])) { + if ( ! isset(self::$_typesMap[$name])) { + throw Doctrine_Exception::unknownType($name); + } + self::$_typeObjects[$name] = new self::$_typesMap[$name](); + } + return self::$_typeObjects[$name]; + } + + /** + * Adds a custom type to the type map. + * + * @param string $name Name of the type. This should correspond to what + * getName() returns. + * @param string $className The class name of the custom type. + */ + public static function addCustomType($name, $className) + { + if (isset(self::$_typesMap[$name])) { + throw Doctrine_Exception::typeExists($name); + } + self::$_typesMap[$name] = $className; + } + + /** + * Overrides an already defined type to use a different implementation. + * + * @param string $name + * @param string $className + */ + public static function overrideType($name, $className) + { + if ( ! isset(self::$_typesMap[$name])) { + throw Doctrine_Exception::typeNotFound($name); + } + self::$_typesMap[$name] = $className; + } +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/DataType/ArrayType.php b/lib/Doctrine/DataType/ArrayType.php new file mode 100644 index 000000000..4a218b2c4 --- /dev/null +++ b/lib/Doctrine/DataType/ArrayType.php @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/lib/Doctrine/DataType/BooleanType.php b/lib/Doctrine/DataType/BooleanType.php new file mode 100644 index 000000000..82b127452 --- /dev/null +++ b/lib/Doctrine/DataType/BooleanType.php @@ -0,0 +1,33 @@ +convertBooleans($value); + } + + /** + * Enter description here... + * + * @param unknown_type $value + * @return unknown + * @override + */ + public function convertToObjectValue($value) + { + return (bool)$value; + } +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/DataType/DateTimeType.php b/lib/Doctrine/DataType/DateTimeType.php new file mode 100644 index 000000000..40ddecab3 --- /dev/null +++ b/lib/Doctrine/DataType/DateTimeType.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/lib/Doctrine/DataType/DecimalType.php b/lib/Doctrine/DataType/DecimalType.php new file mode 100644 index 000000000..c7ffd766f --- /dev/null +++ b/lib/Doctrine/DataType/DecimalType.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/lib/Doctrine/DataType/IntegerType.php b/lib/Doctrine/DataType/IntegerType.php new file mode 100644 index 000000000..5de7624b9 --- /dev/null +++ b/lib/Doctrine/DataType/IntegerType.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/lib/Doctrine/DataType/StringType.php b/lib/Doctrine/DataType/StringType.php new file mode 100644 index 000000000..1f1f566d6 --- /dev/null +++ b/lib/Doctrine/DataType/StringType.php @@ -0,0 +1,28 @@ +getVarcharDeclaration($fieldDeclaration); + } + + public function getDefaultLength(Doctrine_DatabasePlatform $platform) + { + return $platform->getVarcharDefaultLength(); + } + + public function getName() + { + return 'string'; + } +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/DataType/TextType.php b/lib/Doctrine/DataType/TextType.php new file mode 100644 index 000000000..51cd9ad57 --- /dev/null +++ b/lib/Doctrine/DataType/TextType.php @@ -0,0 +1,25 @@ +getClobDeclarationSql($fieldDeclaration); + } + +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/DatabasePlatform.php b/lib/Doctrine/DatabasePlatform.php index 7f7ef14b6..b32dbbb26 100644 --- a/lib/Doctrine/DatabasePlatform.php +++ b/lib/Doctrine/DatabasePlatform.php @@ -31,12 +31,38 @@ * @author Lukas Smith (PEAR MDB2 library) */ abstract class Doctrine_DatabasePlatform -{ +{ + protected $_quoteIdentifiers = false; + + 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' => '' + ); + /** * Constructor. */ public function __construct() {} + public function setQuoteIdentifiers($bool) + { + $this->_quoteIdentifiers = (bool)$bool; + } + + public function getQuoteIdentifiers() + { + return $this->_quoteIdentifiers; + } + /** * Gets the character used for identifier quoting. * @@ -793,7 +819,790 @@ abstract class Doctrine_DatabasePlatform return 'COS(' . $value . ')'; } - /** + /** + * Enter description here... + * + * @return string + */ + public function getForUpdateSql() + { + return 'FOR UPDATE'; + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListDatabasesSql() + { + throw new Doctrine_Export_Exception('List databases not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListFunctionsSql() + { + throw new Doctrine_Export_Exception('List functions not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListTriggersSql() + { + throw new Doctrine_Export_Exception('List triggers not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListSequencesSql() + { + throw new Doctrine_Export_Exception('List sequences not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListTableConstraintsSql() + { + throw new Doctrine_Export_Exception('List table constraints not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListTableColumnsSql() + { + throw new Doctrine_Export_Exception('List table columns not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListTablesSql() + { + throw new Doctrine_Export_Exception('List tables not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListUsersSql() + { + throw new Doctrine_Export_Exception('List users not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getListViewsSql() + { + throw new Doctrine_Export_Exception('List views not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getDropDatabaseSql($database) + { + return 'DROP DATABASE ' . $database; + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getDropTableSql($table) + { + return 'DROP TABLE ' . $table; + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getDropIndexSql($index) + { + return 'DROP INDEX ' . $index; + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getDropSequenceSql() + { + throw new Doctrine_Export_Exception('Drop sequence not supported by this driver.'); + } + + /** + * Gets the SQL for acquiring the next value from a sequence. + */ + public function getSequenceNextValSql($sequenceName) + { + throw new Doctrine_Export_Exception('Sequences not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getCreateDatabaseSql($database) + { + throw new Doctrine_Export_Exception('Create database not supported by this driver.'); + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getCreateTableSql($table, array $columns, array $options) + { + if ( ! $table) { + throw new Doctrine_Export_Exception('no valid table name specified'); + } + if (empty($columns)) { + throw new Doctrine_Export_Exception('no fields specified for table ' . $name); + } + + $queryFields = $this->getFieldDeclarationList($columns); + + 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); + } + } + + $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name, true) . ' (' . $queryFields; + + $check = $this->getCheckDeclaration($columns); + + if ( ! empty($check)) { + $query .= ', ' . $check; + } + + $query .= ')'; + + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $k => $definition) { + if (is_array($definition)) { + $sql[] = $this->getCreateForeignKeySql($name, $definition); + } + } + } + return $sql; + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + */ + public function getCreateSequenceSql($sequenceName, $start = 1, array $options) + { + 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 getCreateConstraintSql($table, $name, $definition) + { + $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[] = $field; + } + $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. + * @see Doctrine_Export::createIndex() + * @return string + */ + public function getCreateIndexSql($table, $name, array $definition) + { + $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[] = $field; + } + $query .= ' (' . implode(', ', $fields) . ')'; + + return $query; + } + + /** + * Quote a string so it can be safely used as a table or column name. + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + pgsql + * + sqlite + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str identifier name to be quoted + * @param bool $checkOption check the 'quote_identifier' option + * + * @return string quoted identifier string + */ + public function quoteIdentifier($str) + { + if ( ! $this->_quoteIdentifiers) { + return $str; + } + + // quick fix for the identifiers that contain a dot + if (strpos($str, '.')) { + $e = explode('.', $str); + return $this->quoteIdentifier($e[0]) + . '.' + . $this->quoteIdentifier($e[1]); + } + + $c = $this->getIdentifierQuoteCharacter(); + $str = str_replace($c, $c . $c, $str); + + return $c . $str . $c; + } + + /** + * 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 getCreateForeignKeySql($table, array $definition) + { + $table = $this->quoteIdentifier($table); + $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSql($definition); + + return $query; + } + + /** + * 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 getAlterTableSql($name, array $changes, $check = false) + { + 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 getFieldDeclarationListSql(array $fields) + { + foreach ($fields as $fieldName => $field) { + $query = $this->getDeclarationSql($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 getDeclarationSql($name, array $field) + { + $default = $this->getDefaultFieldDeclarationSql($field); + + $charset = (isset($field['charset']) && $field['charset']) ? + ' ' . $this->getCharsetFieldDeclarationSql($field['charset']) : ''; + + $collation = (isset($field['collation']) && $field['collation']) ? + ' ' . $this->getCollationFieldDeclarationSql($field['collation']) : ''; + + $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; + + $unique = (isset($field['unique']) && $field['unique']) ? + ' ' . $this->getUniqueFieldDeclarationSql() : ''; + + $check = (isset($field['check']) && $field['check']) ? + ' ' . $field['check'] : ''; + + $method = 'get' . $field['type'] . 'Declaration'; + + if (method_exists($this, $method)) { + return $this->$method($name, $field); + } else { + $dec = $this->getNativeDeclaration($field); + } + + return $this->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 getDefaultFieldDeclarationSql($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') { + $field['default'] = $this->convertBooleans($field['default']); + } + $default = ' DEFAULT ' . $this->quote($field['default'], $field['type']); + } + return $default; + } + + /** + * Obtain DBMS specific SQL code portion needed to set a CHECK constraint + * declaration to be used in statements like CREATE TABLE. + * + * @param array $definition check definition + * @return string DBMS specific SQL code portion needed to set a CHECK constraint + */ + public function getCheckDeclarationSql(array $definition) + { + $constraints = array(); + foreach ($definition as $field => $def) { + if (is_string($def)) { + $constraints[] = 'CHECK (' . $def . ')'; + } else { + if (isset($def['min'])) { + $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')'; + } + + if (isset($def['max'])) { + $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')'; + } + } + } + + return implode(', ', $constraints); + } + + /** + * Obtain DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param string $name name of the index + * @param array $definition index definition + * @return string DBMS specific SQL code portion needed to set an index + */ + public function getIndexDeclarationSql($name, array $definition) + { + $name = $this->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->getIndexFieldDeclarationListSql($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 getIndexFieldDeclarationListSql(array $fields) + { + $ret = array(); + foreach ($fields as $field => $definition) { + if (is_array($definition)) { + $ret[] = $this->quoteIdentifier($field); + } else { + $ret[] = $this->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 getTemporaryTableSql() + { + return 'TEMPORARY'; + } + + /** + * Enter description here... + * + * @return unknown + */ + public function getShowDatabasesSql() + { + throw new Doctrine_Export_Exception('Show databases not supported by this driver.'); + } + + /** + * 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 getForeignKeyDeclarationSql(array $definition) + { + $sql = $this->getForeignKeyBaseDeclarationSql($definition); + $sql .= $this->getAdvancedForeignKeyOptionsSql($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 getAdvancedForeignKeyOptionsSql(array $definition) + { + $query = ''; + if ( ! empty($definition['onUpdate'])) { + $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSql($definition['onUpdate']); + } + if ( ! empty($definition['onDelete'])) { + $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSql($definition['onDelete']); + } + return $query; + } + + /** + * 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 getForeignKeyReferentialActionSql($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.'); + } + } + + /** + * 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 getForeignKeyBaseDeclarationSql(array $definition) + { + $sql = ''; + if (isset($definition['name'])) { + $sql .= ' CONSTRAINT ' . $this->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, 'quoteIdentifier'), $definition['local'])) + . ') REFERENCES ' + . $this->_conn->quoteIdentifier($definition['foreignTable']) . '(' + . implode(', ', array_map(array($this, '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 getUniqueFieldDeclarationSql() + { + 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 getCharsetFieldDeclarationSql($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 getCollationFieldDeclarationSql($collation) + { + return ''; + } + + /** * build a pattern matching string * * EXPERIMENTAL @@ -838,7 +1647,7 @@ abstract class Doctrine_DatabasePlatform * @return string DBMS specific SQL code portion that should be used to * declare the specified field. */ - abstract public function getNativeDeclaration($field); + abstract public function getNativeDeclaration(array $field); /** * Maps a native array description of a field to a Doctrine datatype and length @@ -931,6 +1740,31 @@ abstract class Doctrine_DatabasePlatform return $this->_properties[$name]; } + /** + * Some platforms need the boolean values to be converted. + * Default conversion defined here converts to integers. + * + * @param array $item + * @return void + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $k => $value) { + if (is_bool($value)) { + $item[$k] = (int) $value; + } + } + } else { + if (is_bool($item)) { + $item = (int) $item; + } + } + return $item; + } + + /* supports*() metods */ + public function supportsSequences() { return false; diff --git a/lib/Doctrine/DatabasePlatform/MockPlatform.php b/lib/Doctrine/DatabasePlatform/MockPlatform.php new file mode 100644 index 000000000..a00a6ded5 --- /dev/null +++ b/lib/Doctrine/DatabasePlatform/MockPlatform.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/lib/Doctrine/DatabasePlatform/MySqlPlatform.php b/lib/Doctrine/DatabasePlatform/MySqlPlatform.php index 90cd6503e..8cdddeddc 100644 --- a/lib/Doctrine/DatabasePlatform/MySqlPlatform.php +++ b/lib/Doctrine/DatabasePlatform/MySqlPlatform.php @@ -230,7 +230,7 @@ class Doctrine_DatabasePlatform_MySqlPlatform extends Doctrine_DatabasePlatform /** * @TEST */ - public function getVarcharDeclaration(array $field) + public function getVarcharDeclarationSql(array $field) { if ( ! isset($field['length'])) { if (array_key_exists('default', $field)) { @@ -247,6 +247,26 @@ class Doctrine_DatabasePlatform_MySqlPlatform extends Doctrine_DatabasePlatform : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); } + /** + * Enter description here... + * + * @param array $field + */ + public function getClobDeclarationSql(array $field) + { + if ( ! empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYTEXT'; + } else if ($length <= 65532) { + return 'TEXT'; + } else if ($length <= 16777215) { + return 'MEDIUMTEXT'; + } + } + return 'LONGTEXT'; + } + /** * Obtain DBMS specific SQL code portion needed to declare an text type * field to be used in statements like CREATE TABLE. @@ -271,7 +291,7 @@ class Doctrine_DatabasePlatform_MySqlPlatform extends Doctrine_DatabasePlatform * declare the specified field. * @override */ - public function getNativeDeclaration($field) + public function getNativeDeclaration(array $field) { if ( ! isset($field['type'])) { throw new Doctrine_DataDict_Exception('Missing column type.'); @@ -287,31 +307,9 @@ class Doctrine_DatabasePlatform_MySqlPlatform extends Doctrine_DatabasePlatform case 'object': case 'string': case 'gzip': - if ( ! isset($field['length'])) { - if (array_key_exists('default', $field)) { - $field['length'] = $this->conn->varchar_max_length; - } else { - $field['length'] = false; - } - } - - $length = ($field['length'] <= $this->conn->varchar_max_length) ? $field['length'] : false; - $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; - - return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') - : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); + return $this->getVarcharDeclarationSql($field); case 'clob': - if ( ! empty($field['length'])) { - $length = $field['length']; - if ($length <= 255) { - return 'TINYTEXT'; - } elseif ($length <= 65532) { - return 'TEXT'; - } elseif ($length <= 16777215) { - return 'MEDIUMTEXT'; - } - } - return 'LONGTEXT'; + return $this->getClobDeclarationSql($field); case 'blob': if ( ! empty($field['length'])) { $length = $field['length']; @@ -592,6 +590,648 @@ class Doctrine_DatabasePlatform_MySqlPlatform extends Doctrine_DatabasePlatform { return false; } + + /** + * Enter description here... + * + * @return unknown + * @override + */ + public function getShowDatabasesSql() + { + return 'SHOW DATABASES'; + } + + /** + * Enter description here... + * + * @todo Throw exception by default? + * @override + */ + public function getListTablesSql() + { + return 'SHOW TABLES'; + } + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @return string + * @override + */ + public function getCreateDatabaseSql($name) + { + return 'CREATE DATABASE ' . $this->quoteIdentifier($name); + } + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return string + * @override + */ + public function getDropDatabaseSql($name) + { + return 'DROP DATABASE ' . $this->quoteIdentifier($name); + } + + /** + * 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: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * + * @return void + * @override + */ + public function getCreateTableSql($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->getFieldDeclarationListSql($fields); + + // build indexes for all foreign key fields (needed in MySQL!!) + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $fk) { + $local = $fk['local']; + $found = false; + if (isset($options['indexes'])) { + foreach ($options['indexes'] as $definition) { + if (is_string($definition['fields'])) { + // Check if index already exists on the column + $found = ($local == $definition['fields']); + } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) { + // Index already exists on the column + $found = true; + } + } + } + if (isset($options['primary']) && !empty($options['primary']) && + in_array($local, $options['primary'])) { + // field is part of the PK and therefore already indexed + $found = true; + } + + if ( ! $found) { + $options['indexes'][$local] = array('fields' => array($local => array())); + } + } + } + + // add all indexes + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $queryFields .= ', ' . $this->getIndexDeclarationSql($index, $definition); + } + } + + // attach all primary keys + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_values($options['primary']); + $keyColumns = array_map(array($this->_conn, 'quoteIdentifier'), $keyColumns); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE '; + if (!empty($options['temporary'])) { + $query .= 'TEMPORARY '; + } + $query.= 'TABLE ' . $this->quoteIdentifier($name, true) . ' (' . $queryFields . ')'; + + $optionStrings = array(); + + if (isset($options['comment'])) { + $optionStrings['comment'] = 'COMMENT = ' . $this->quote($options['comment'], 'text'); + } + if (isset($options['charset'])) { + $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset']; + if (isset($options['collate'])) { + $optionStrings['charset'] .= ' COLLATE ' . $options['collate']; + } + } + + $type = false; + + // get the type of the table + if (isset($options['type'])) { + $type = $options['type']; + } else { + $type = $this->getAttribute(Doctrine::ATTR_DEFAULT_TABLE_TYPE); + } + + if ($type) { + $optionStrings[] = 'ENGINE = ' . $type; + } + + if ( ! empty($optionStrings)) { + $query.= ' '.implode(' ', $optionStrings); + } + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $k => $definition) { + if (is_array($definition)) { + $sql[] = $this->getCreateForeignKeySql($name, $definition); + } + } + } + + return $sql; + } + + /** + * alter an existing table + * + * @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 Metabase 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 Metabase 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 boolean + * @override + */ + public function getAlterTableSql($name, array $changes, $check = false) + { + if ( ! $name) { + throw new Doctrine_Export_Exception('no valid table name specified'); + } + foreach ($changes as $changeName => $change) { + switch ($changeName) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + throw new Doctrine_Export_Exception('change type "' . $changeName . '" not yet supported'); + } + } + + if ($check) { + return true; + } + + $query = ''; + if ( ! empty($changes['name'])) { + $change_name = $this->quoteIdentifier($changes['name']); + $query .= 'RENAME TO ' . $change_name; + } + + if ( ! empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $fieldName => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $this->getDeclarationSql($fieldName, $field); + } + } + + if ( ! empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $fieldName => $field) { + if ($query) { + $query .= ', '; + } + $fieldName = $this->quoteIdentifier($fieldName); + $query .= 'DROP ' . $fieldName; + } + } + + $rename = array(); + if ( ! empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $fieldName => $field) { + $rename[$field['name']] = $fieldName; + } + } + + if ( ! empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $fieldName => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$fieldName])) { + $oldFieldName = $rename[$fieldName]; + unset($rename[$fieldName]); + } else { + $oldFieldName = $fieldName; + } + $oldFieldName = $this->quoteIdentifier($oldFieldName, true); + $query .= 'CHANGE ' . $oldFieldName . ' ' + . $this->getDeclarationSql($fieldName, $field['definition']); + } + } + + if ( ! empty($rename) && is_array($rename)) { + foreach ($rename as $renameName => $renamedField) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamedField]; + $renamedField = $this->quoteIdentifier($renamedField, true); + $query .= 'CHANGE ' . $renamedField . ' ' + . $this->getDeclarationSql($field['name'], $field['definition']); + } + } + + if ( ! $query) { + return false; + } + + $name = $this->quoteIdentifier($name, true); + + return 'ALTER TABLE ' . $name . ' ' . $query; + } + + /** + * Get the stucture of a field into an array + * + * @author Leoncx + * @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' => 'ASC' + * 'length' => 10 + * ), + * 'last_login' => array() + * ) + * ) + * @throws PDOException + * @return void + * @override + */ + public function getCreateIndexSql($table, $name, array $definition) + { + $table = $table; + $name = $this->formatter->getIndexName($name); + $name = $this->quoteIdentifier($name); + $type = ''; + if (isset($definition['type'])) { + switch (strtolower($definition['type'])) { + case 'fulltext': + 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; + $query .= ' (' . $this->getIndexFieldDeclarationListSql($definition['fields']) . ')'; + + return $query; + } + + /** + * 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 string $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. + * @override + */ + public function getIntegerDeclarationSql($name, $field) + { + $default = $autoinc = ''; + if ( ! empty($field['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + if (is_null($field['default'])) { + $default = ' DEFAULT NULL'; + } else { + $default = ' DEFAULT '.$this->quote($field['default']); + } + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; + $unsigned = (isset($field['unsigned']) && $field['unsigned']) ? ' UNSIGNED' : ''; + + $name = $this->quoteIdentifier($name, true); + + return $name . ' ' . $this->getNativeDeclaration($field) . $unsigned . $default . $notnull . $autoinc; + } + + /** + * 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 + * @override + */ + public function getDefaultFieldDeclarationSql($field) + { + $default = empty($field['notnull']) && !in_array($field['type'], array('clob', 'blob')) + ? ' DEFAULT NULL' : ''; + + if (isset($field['default']) && ( ! isset($field['length']) || $field['length'] <= 255)) { + if ($field['default'] === '') { + $field['default'] = null; + if (! empty($field['notnull']) && array_key_exists($field['type'], $this->valid_default_values)) { + $field['default'] = $this->valid_default_values[$field['type']]; + } + + if ($field['default'] === '' + && ($this->_conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL) + ) { + $field['default'] = ' '; + } + } + + if ($field['type'] == 'enum' && $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)) { + $fieldType = 'varchar'; + } else { + if ($field['type'] === 'boolean') { + $fields['default'] = $this->convertBooleans($field['default']); + } + $fieldType = $field['type']; + } + + $default = ' DEFAULT ' . $this->quote($field['default'], $fieldType); + } + 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 + * @override + */ + public function getIndexDeclarationSql($name, array $definition) + { + $name = $this->formatter->getIndexName($name); + $type = ''; + if (isset($definition['type'])) { + switch (strtolower($definition['type'])) { + case 'fulltext': + case 'unique': + $type = strtoupper($definition['type']) . ' '; + break; + default: + throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']); + } + } + + if ( ! isset($definition['fields'])) { + throw new Doctrine_Export_Exception('No index columns given.'); + } + if ( ! is_array($definition['fields'])) { + $definition['fields'] = array($definition['fields']); + } + + $query = $type . 'INDEX ' . $this->quoteIdentifier($name); + + $query .= ' (' . $this->getIndexFieldDeclarationListSql($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 + * @override + */ + public function getIndexFieldDeclarationListSql(array $fields) + { + $declFields = array(); + + foreach ($fields as $fieldName => $field) { + $fieldString = $this->quoteIdentifier($fieldName); + + if (is_array($field)) { + if (isset($field['length'])) { + $fieldString .= '(' . $field['length'] . ')'; + } + + if (isset($field['sorting'])) { + $sort = strtoupper($field['sorting']); + switch ($sort) { + case 'ASC': + case 'DESC': + $fieldString .= ' ' . $sort; + break; + default: + throw new Doctrine_Export_Exception('Unknown index sorting option given.'); + } + } + } else { + $fieldString = $this->quoteIdentifier($field); + } + $declFields[] = $fieldString; + } + return implode(', ', $declFields); + } + + /** + * getAdvancedForeignKeyOptions + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @override + */ + public function getAdvancedForeignKeyOptionsSql(array $definition) + { + $query = ''; + if ( ! empty($definition['match'])) { + $query .= ' MATCH ' . $definition['match']; + } + if ( ! empty($definition['onUpdate'])) { + $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSql($definition['onUpdate']); + } + if ( ! empty($definition['onDelete'])) { + $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSql($definition['onDelete']); + } + return $query; + } + + /** + * 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 + * @override + */ + public function getDropIndexSql($table, $name) + { + $table = $this->quoteIdentifier($table, true); + $name = $this->quoteIdentifier($this->formatter->getIndexName($name), true); + return 'DROP INDEX ' . $name . ' ON ' . $table; + } + + /** + * dropTable + * + * @param string $table name of table that should be dropped from the database + * @throws PDOException + * @return void + * @override + */ + public function getDropTableSql($table) + { + $table = $this->quoteIdentifier($table, true); + return 'DROP TABLE ' . $table; + } } ?> \ No newline at end of file diff --git a/lib/Doctrine/DatabasePlatform/PostgreSqlPlatform.php b/lib/Doctrine/DatabasePlatform/PostgreSqlPlatform.php index 75b30ed88..8fafe54ee 100644 --- a/lib/Doctrine/DatabasePlatform/PostgreSqlPlatform.php +++ b/lib/Doctrine/DatabasePlatform/PostgreSqlPlatform.php @@ -520,6 +520,474 @@ class Doctrine_DatabasePlatform_PostgreSqlPlatform extends Doctrine_DatabasePlat { return true; } + + /** + * Enter description here... + * + * @override + */ + public function getListDatabasesSql() + { + return 'SELECT datname FROM pg_database'; + } + + /** + * Enter description here... + * + * @override + */ + public function getListFunctionsSql() + { + return "SELECT + proname + FROM + pg_proc pr, pg_type tp + WHERE + tp.oid = pr.prorettype + AND pr.proisagg = FALSE + AND tp.typname <> 'trigger' + AND pr.pronamespace IN + (SELECT oid FROM pg_namespace + WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema'"; + } + + /** + * Enter description here... + * + * @override + */ + public function getListSequencesSql() + { + return "SELECT + relname + FROM + pg_class + WHERE relkind = 'S' AND relnamespace IN + (SELECT oid FROM pg_namespace + WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; + } + + /** + * Enter description here... + * + * @override + */ + public function getListTablesSql() + { + return "SELECT + c.relname AS table_name + FROM pg_class c, pg_user u + WHERE c.relowner = u.usesysid + AND c.relkind = 'r' + AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) + AND c.relname !~ '^(pg_|sql_)' + UNION + SELECT c.relname AS table_name + FROM pg_class c + WHERE c.relkind = 'r' + AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) + AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) + AND c.relname !~ '^pg_'"; + } + + /** + * Enter description here... + * + * @override + */ + public function getListViewsSql() + { + return 'SELECT viewname FROM pg_views'; + } + + /** + * Enter description here... + * + * @override + */ + public function getListUsersSql() + { + return 'SELECT usename FROM pg_user'; + } + + /** + * Enter description here... + * + * @override + */ + public function getListTableConstraintsSql() + { + return "SELECT + relname + FROM + pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = %s + AND pg_class.oid = pg_index.indrelid + AND (indisunique = 't' OR indisprimary = 't') + )"; + } + + /** + * Enter description here... + * + * @override + */ + public function getListTableIndexesSql() + { + return "SELECT + relname + FROM + pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = %s + AND pg_class.oid=pg_index.indrelid + AND indisunique != 't' + AND indisprimary != 't' + )"; + } + + /** + * Enter description here... + * + * @override + */ + public function getListTableColumnsSql() + { + return "SELECT + a.attnum, + a.attname AS field, + t.typname AS type, + format_type(a.atttypid, a.atttypmod) AS complete_type, + a.attnotnull AS isnotnull, + (SELECT 't' + FROM pg_index + WHERE c.oid = pg_index.indrelid + AND pg_index.indkey[0] = a.attnum + AND pg_index.indisprimary = 't' + ) AS pri, + (SELECT pg_attrdef.adsrc + FROM pg_attrdef + WHERE c.oid = pg_attrdef.adrelid + AND pg_attrdef.adnum=a.attnum + ) AS default + FROM pg_attribute a, pg_class c, pg_type t + WHERE c.relname = %s + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + ORDER BY a.attnum"; + } + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @throws PDOException + * @return void + * @override + */ + public function getCreateDatabaseSql($name) + { + return 'CREATE DATABASE ' . $this->quoteIdentifier($name); + } + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @throws PDOException + * @access public + */ + public function getDropDatabaseSql($name) + { + return 'DROP DATABASE ' . $this->quoteIdentifier($name); + } + + /** + * 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 + * @override + */ + public function getAdvancedForeignKeyOptionsSql(array $definition) + { + $query = ''; + if (isset($definition['match'])) { + $query .= ' MATCH ' . $definition['match']; + } + if (isset($definition['onUpdate'])) { + $query .= ' ON UPDATE ' . $definition['onUpdate']; + } + if (isset($definition['onDelete'])) { + $query .= ' ON DELETE ' . $definition['onDelete']; + } + if (isset($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (isset($definition['feferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + return $query; + } + + /** + * generates the sql for altering an existing table on postgresql + * + * @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 array + * @override + */ + public function getAlterTableSql($name, array $changes, $check = false) + { + foreach ($changes as $changeName => $change) { + switch ($changeName) { + case 'add': + case 'remove': + case 'change': + case 'name': + case 'rename': + break; + default: + throw new Doctrine_Export_Exception('change type "' . $changeName . '\" not yet supported'); + } + } + + if ($check) { + return true; + } + + $sql = array(); + + if (isset($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $fieldName => $field) { + $query = 'ADD ' . $this->getDeclarationSql($fieldName, $field); + $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; + } + } + + if (isset($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $fieldName => $field) { + $fieldName = $this->quoteIdentifier($fieldName, true); + $query = 'DROP ' . $fieldName; + $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; + } + } + + if (isset($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $fieldName => $field) { + $fieldName = $this->quoteIdentifier($fieldName, true); + if (isset($field['type'])) { + $serverInfo = $this->getServerVersion(); + + if (is_array($serverInfo) && $serverInfo['major'] < 8) { + throw new Doctrine_Export_Exception('changing column type for "'.$field['type'].'\" requires PostgreSQL 8.0 or above'); + } + $query = 'ALTER ' . $fieldName . ' TYPE ' . $this->getTypeDeclarationSql($field['definition']); + $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; + } + if (array_key_exists('default', $field)) { + $query = 'ALTER ' . $fieldName . ' SET DEFAULT ' . $this->quote($field['definition']['default'], $field['definition']['type']); + $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; + } + if ( ! empty($field['notnull'])) { + $query = 'ALTER ' . $fieldName . ' ' . ($field['definition']['notnull'] ? 'SET' : 'DROP') . ' NOT NULL'; + $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; + } + } + } + + if (isset($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $fieldName => $field) { + $fieldName = $this->quoteIdentifier($fieldName, true); + $sql[] = 'ALTER TABLE ' . $name . ' RENAME COLUMN ' . $fieldName . ' TO ' . $this->quoteIdentifier($field['name'], true); + } + } + + $name = $this->quoteIdentifier($name, true); + if (isset($changes['name'])) { + $changeName = $this->quoteIdentifier($changes['name'], true); + $sql[] = 'ALTER TABLE ' . $name . ' RENAME TO ' . $changeName; + } + + return $sql; + } + + /** + * return RDBMS specific create sequence statement + * + * @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 + * @override + */ + public function getCreateSequenceSql($sequenceName, $start = 1, array $options = array()) + { + $sequenceName = $this->quoteIdentifier($this->formatter->getSequenceName($sequenceName), true); + return 'CREATE SEQUENCE ' . $sequenceName . ' INCREMENT 1' . + ($start < 1 ? ' MINVALUE ' . $start : '') . ' START ' . $start; + } + + /** + * drop existing sequence + * + * @param string $sequenceName name of the sequence to be dropped + * @override + */ + public function getDropSequenceSql($sequenceName) + { + $sequenceName = $this->quoteIdentifier($this->formatter->getSequenceName($sequenceName), true); + return 'DROP SEQUENCE ' . $sequenceName; + } + + /** + * Gets the SQL used to create a table. + * + * @param unknown_type $name + * @param array $fields + * @param array $options + * @return unknown + */ + public function getCreateTableSql($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->getFieldDeclarationListSql($fields); + + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_values($options['primary']); + $keyColumns = array_map(array($this, 'quoteIdentifier'), $keyColumns); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE TABLE ' . $this->quoteIdentifier($name, true) . ' (' . $queryFields . ')'; + + $sql[] = $query; + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $sql[] = $this->getCreateIndexSql($name, $index, $definition); + } + } + + if (isset($options['foreignKeys'])) { + + foreach ((array) $options['foreignKeys'] as $k => $definition) { + if (is_array($definition)) { + $sql[] = $this->getCreateForeignKeySql($name, $definition); + } + } + } + + return $sql; + } + + /** + * 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 getIntegerDeclarationSql($name, $field) + { + if ( ! empty($field['autoincrement'])) { + $name = $this->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->quote($field['default'], $field['type']); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $this->quoteIdentifier($name, true); + + return $name . ' ' . $this->getNativeDeclaration($field) . $default . $notnull; + } + + /** + * Postgres wants boolean values converted to the strings 'true'/'false'. + * + * @param array $item + * @return void + * @override + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + if (is_bool($value) || is_numeric($item)) { + $item[$key] = ($value) ? 'true' : 'false'; + } + } + } else { + if (is_bool($item) || is_numeric($item)) { + $item = ($item) ? 'true' : 'false'; + } + } + return $item; + } + + /** + * Enter description here... + * + * @param string $sequenceName + * @override + */ + public function getSequenceNextValSql($sequenceName) + { + return "SELECT NEXTVAL('" . $sequenceName . "')"; + } } ?> \ No newline at end of file diff --git a/lib/Doctrine/Entity.php b/lib/Doctrine/Entity.php index 3b9c8f520..71498c692 100644 --- a/lib/Doctrine/Entity.php +++ b/lib/Doctrine/Entity.php @@ -429,8 +429,8 @@ abstract class Doctrine_Entity implements ArrayAccess, Serializable if ($this->_class->hasField($fieldName)) { $old = isset($this->_data[$fieldName]) ? $this->_data[$fieldName] : null; // NOTE: Common case: $old != $value. Special case: null == 0 (TRUE), which - // is addressed by the type comparison. - if ($old != $value || gettype($old) != gettype($value)) { + // is addressed by xor. + if ($old != $value || (is_null($old) xor is_null($value))) { $this->_data[$fieldName] = $value; $this->_dataChangeSet[$fieldName] = array($old => $value); if ($this->isNew() && $this->_class->isIdentifier($fieldName)) { @@ -456,7 +456,10 @@ abstract class Doctrine_Entity implements ArrayAccess, Serializable throw Doctrine_Entity_Exception::invalidField($fieldName); } } - + + /** + * Registers the entity as dirty with the UnitOfWork. + */ private function _registerDirty() { if ($this->_state == self::STATE_MANAGED && @@ -531,35 +534,33 @@ abstract class Doctrine_Entity implements ArrayAccess, Serializable */ final public function _internalSetReference($name, $value, $completeBidirectional = false) { - if ($value === Doctrine_Null::$INSTANCE) { + if (is_null($value) || $value === Doctrine_Null::$INSTANCE) { $this->_references[$name] = $value; - return; + return; // early exit! } $rel = $this->_class->getAssociationMapping($name); if ($rel->isOneToOne() && ! $value instanceof Doctrine_Entity) { throw Doctrine_Entity_Exception::invalidValueForOneToOneReference(); - } - if ($rel->isOneToMany() && ! $value instanceof Doctrine_Collection) { + } else if (($rel->isOneToMany() || $rel->isManyToMany()) && ! $value instanceof Doctrine_Collection) { throw Doctrine_Entity_Exception::invalidValueForOneToManyReference(); } - if ($rel->isManyToMany() && ! $value instanceof Doctrine_Collection) { - throw Doctrine_Entity_Exception::invalidValueForManyToManyReference(); - } $this->_references[$name] = $value; if ($completeBidirectional && $rel->isOneToOne()) { - //TODO: check if $rel is bidirectional, if yes create the back-reference if ($rel->isOwningSide()) { - //TODO: how to check if its bidirectional? should be as efficient as possible - /*$targetClass = $this->_em->getClassMetadata($rel->getTargetEntityName()); - if (($invAssoc = $targetClass->getInverseAssociation($name)) !== null) { - $value->_internalSetReference($invAssoc->getSourceFieldName(), $this); - }*/ + // If there is an inverse mapping on the target class its bidirectional + $targetClass = $this->_em->getClassMetadata($rel->getTargetEntityName()); + if ($targetClass->hasInverseAssociationMapping($name)) { + $value->_internalSetReference( + $targetClass->getInverseAssociationMapping($name)->getSourceFieldName(), + $this + ); + } } else { - // for sure bi-directional, as there is no inverse side in unidirectional + // for sure bidirectional, as there is no inverse side in unidirectional $value->_internalSetReference($rel->getMappedByFieldName(), $this); } } diff --git a/lib/Doctrine/EntityManager.php b/lib/Doctrine/EntityManager.php index eaeedf0fd..9351e5cd2 100644 --- a/lib/Doctrine/EntityManager.php +++ b/lib/Doctrine/EntityManager.php @@ -653,6 +653,35 @@ class Doctrine_EntityManager return $this->_unitOfWork; } + /** + * Enter description here... + * + * @param unknown_type $type + * @param unknown_type $class + */ + /*public function getIdGenerator($class) + { + $type = $class->getIdGeneratorType(); + if ($type == Doctrine_ClassMetadata::GENERATOR_TYPE_IDENTITY) { + if ( ! isset($this->_idGenerators[$type])) { + $this->_idGenerators[$type] = new Doctrine_Id_IdentityGenerator($this); + } + } else if ($type == Doctrine_ClassMetadata::GENERATOR_TYPE_SEQUENCE) { + if ( ! isset($this->_idGenerators[$type])) { + $this->_idGenerators[$type] = new Doctrine_Id_SequenceGenerator($this); + } + } else if ($type == Doctrine_ClassMetadata::GENERATOR_TYPE_TABLE) { + if ( ! isset($this->_idGenerators[$type])) { + $this->_idGenerators[$type] = new Doctrine_Id_TableGenerator($this); + } + } + + $generator = $this->_idGenerators[$type]; + $generator->configureForClass($class); + + return $generator; + }*/ + } ?> \ No newline at end of file diff --git a/lib/Doctrine/EntityManagerFactory.php b/lib/Doctrine/EntityManagerFactory.php index 72d371aca..ef1e4c3de 100644 --- a/lib/Doctrine/EntityManagerFactory.php +++ b/lib/Doctrine/EntityManagerFactory.php @@ -59,7 +59,7 @@ class Doctrine_EntityManagerFactory */ public function __construct() { - $this->_connFactory = new Doctrine_ConnectionFactory(); + } /** @@ -93,16 +93,20 @@ class Doctrine_EntityManagerFactory */ public function createEntityManager($connParams, $name = null) { - if ( ! $this->_config) { - $this->_config = new Doctrine_Configuration(); - } - if ( ! $this->_eventManager) { - $this->_eventManager = new Doctrine_EventManager(); + if ( ! $this->_connFactory) { + // Initialize connection factory + $this->_connFactory = new Doctrine_ConnectionFactory(); + if ( ! $this->_config) { + $this->_config = new Doctrine_Configuration(); + } + if ( ! $this->_eventManager) { + $this->_eventManager = new Doctrine_EventManager(); + } + $this->_connFactory->setConfiguration($this->_config); + $this->_connFactory->setEventManager($this->_eventManager); } $conn = $this->_connFactory->createConnection($connParams); - $conn->setEventManager($this->_eventManager); - $conn->setConfiguration($this->_config); $em = new Doctrine_EntityManager($conn); $em->setEventManager($this->_eventManager); diff --git a/lib/Doctrine/EntityPersister/Abstract.php b/lib/Doctrine/EntityPersister/Abstract.php index d5738800a..6dfeb36b0 100644 --- a/lib/Doctrine/EntityPersister/Abstract.php +++ b/lib/Doctrine/EntityPersister/Abstract.php @@ -123,6 +123,7 @@ abstract class Doctrine_EntityPersister_Abstract if ($class->isIdGeneratorIdentity()) { //TODO: Postgres IDENTITY columns (SERIAL) use a sequence, so we need to pass the // sequence name to lastInsertId(). + //TODO: $this->_em->getIdGenerator($class)->generate(); $entity->_assignIdentifier($this->_conn->lastInsertId()); } } @@ -312,6 +313,8 @@ abstract class Doctrine_EntityPersister_Abstract default: $result[$columnName] = $newVal; } + /*$result[$columnName] = $type->convertToDatabaseValue( + $newVal, $this->_em->getConnection()->getDatabasePlatform());*/ } // @todo Cleanup diff --git a/lib/Doctrine/EntityRepository.php b/lib/Doctrine/EntityRepository.php index 496addf48..1fb7b30d5 100644 --- a/lib/Doctrine/EntityRepository.php +++ b/lib/Doctrine/EntityRepository.php @@ -96,6 +96,8 @@ class Doctrine_EntityRepository $values = is_array($id) ? array_values($id) : array($id); $keys = $this->_classMetadata->getIdentifier(); } + + //TODO: check identity map? return $this->_createQuery() ->where(implode(' = ? AND ', $keys) . ' = ?') diff --git a/lib/Doctrine/Hydrator/RecordDriver.php b/lib/Doctrine/Hydrator/RecordDriver.php index 894d5563e..77383eb0a 100644 --- a/lib/Doctrine/Hydrator/RecordDriver.php +++ b/lib/Doctrine/Hydrator/RecordDriver.php @@ -73,7 +73,8 @@ class Doctrine_Hydrator_RecordDriver $relation = $entity->getClass()->getAssociationMapping($name); $relatedClass = $this->_em->getClassMetadata($relation->getTargetEntityName()); $coll = $this->getElementCollection($relatedClass->getClassName()); - $coll->setReference($entity, $relation); + $coll->_setOwner($entity, $relation); + $coll->_setHydrationFlag(true); $entity->_internalSetReference($name, $coll, true); $this->_initializedRelations[$entity->getOid()][$name] = true; } @@ -145,7 +146,8 @@ class Doctrine_Hydrator_RecordDriver { // take snapshots from all initialized collections foreach ($this->_collections as $coll) { - $coll->takeSnapshot(); + $coll->_takeSnapshot(); + $coll->_setHydrationFlag(false); } $this->_collections = array(); $this->_initializedRelations = array(); diff --git a/lib/Doctrine/HydratorNew.php b/lib/Doctrine/HydratorNew.php index 6facb152e..e05f35411 100644 --- a/lib/Doctrine/HydratorNew.php +++ b/lib/Doctrine/HydratorNew.php @@ -400,6 +400,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $rowData[$dqlAlias][$fieldName] = $this->prepareValue( $class, $fieldName, $value, $cache[$key]['type']); } + //$rowData[$dqlAlias][$fieldName] = $cache[$key]['type']->convertToObjectValue($value); if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { $nonemptyComponents[$dqlAlias] = true; @@ -468,6 +469,7 @@ class Doctrine_HydratorNew extends Doctrine_Hydrator_Abstract $rowData[$dqlAlias . '_' . $fieldName] = $this->prepareValue( $class, $fieldName, $value, $cache[$key]['type']); } + //$rowData[$dqlAlias . '_' . $fieldName] = $cache[$key]['type']->convertToObjectValue($value); } return $rowData; diff --git a/lib/Doctrine/Id/AbstractIdGenerator.php b/lib/Doctrine/Id/AbstractIdGenerator.php new file mode 100644 index 000000000..1e55c7dd1 --- /dev/null +++ b/lib/Doctrine/Id/AbstractIdGenerator.php @@ -0,0 +1,24 @@ +_em = $em; + } + + abstract public function configureForClass(Doctrine_ClassMetadata $class); + + abstract public function generate(); +} + +?> \ No newline at end of file diff --git a/lib/Doctrine/Id/IdentityGenerator.php b/lib/Doctrine/Id/IdentityGenerator.php new file mode 100644 index 000000000..6c9b33c3f --- /dev/null +++ b/lib/Doctrine/Id/IdentityGenerator.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/lib/Doctrine/Id/SequenceGenerator.php b/lib/Doctrine/Id/SequenceGenerator.php new file mode 100644 index 000000000..15c5adc7f --- /dev/null +++ b/lib/Doctrine/Id/SequenceGenerator.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/lib/Doctrine/Id/TableGenerator.php b/lib/Doctrine/Id/TableGenerator.php new file mode 100644 index 000000000..15c5adc7f --- /dev/null +++ b/lib/Doctrine/Id/TableGenerator.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/lib/Doctrine/Import.php b/lib/Doctrine/Import.php index 4588559d3..67cf73f0e 100644 --- a/lib/Doctrine/Import.php +++ b/lib/Doctrine/Import.php @@ -19,7 +19,7 @@ * . */ -#namespace Doctrine::DBAL::Import; +#namespace Doctrine::ORM::Import; /** * class Doctrine_Import @@ -64,11 +64,11 @@ class Doctrine_Import extends Doctrine_Connection_Module $builder->setOptions($options); $classes = array(); - foreach ($connection->import->listTables() as $table) { + foreach ($connection->getSchemaManager()->listTables() as $table) { $definition = array(); $definition['tableName'] = $table; $definition['className'] = Doctrine_Inflector::classify($table); - $definition['columns'] = $connection->import->listTableColumns($table); + $definition['columns'] = $connection->getSchemaManager()->listTableColumns($table); $builder->buildRecord($definition); diff --git a/lib/Doctrine/Query/AbstractResult.php b/lib/Doctrine/Query/AbstractResult.php index d537777a5..1cf451006 100755 --- a/lib/Doctrine/Query/AbstractResult.php +++ b/lib/Doctrine/Query/AbstractResult.php @@ -27,7 +27,7 @@ * @subpackage Query * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.com - * @since 1.0 + * @since 2.0 * @version $Revision: 1393 $ * @author Guilherme Blanco * @author Konsta Vesterinen diff --git a/lib/Doctrine/Query/Parser.php b/lib/Doctrine/Query/Parser.php index 1266f1aef..f3e0ab5d6 100644 --- a/lib/Doctrine/Query/Parser.php +++ b/lib/Doctrine/Query/Parser.php @@ -238,7 +238,7 @@ class Doctrine_Query_Parser throw new Doctrine_Query_Parser_Exception(implode("\r\n", $this->_errors)); } - // Assign the SQL executor in parser result + // Assign the executor in parser result $this->_parserResult->setSqlExecutor(Doctrine_Query_SqlExecutor_Abstract::create($AST)); return $this->_parserResult; diff --git a/lib/Doctrine/Query/ParserResult.php b/lib/Doctrine/Query/ParserResult.php index 85afdaf7e..4a9213223 100755 --- a/lib/Doctrine/Query/ParserResult.php +++ b/lib/Doctrine/Query/ParserResult.php @@ -19,8 +19,6 @@ * . */ -Doctrine::autoload('Doctrine_Query_AbstractResult'); - /** * Doctrine_Query_ParserResult * diff --git a/lib/Doctrine/Query/QueryResult.php b/lib/Doctrine/Query/QueryResult.php index fb09e3474..6bd17e497 100755 --- a/lib/Doctrine/Query/QueryResult.php +++ b/lib/Doctrine/Query/QueryResult.php @@ -19,8 +19,6 @@ * . */ -Doctrine::autoload('Doctrine_Query_AbstractResult'); - /** * Doctrine_Query_QueryResult * @@ -30,7 +28,7 @@ Doctrine::autoload('Doctrine_Query_AbstractResult'); * @author Janne Vanhala * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link http://www.phpdoctrine.org - * @since 1.0 + * @since 2.0 * @version $Revision$ */ class Doctrine_Query_QueryResult extends Doctrine_Query_AbstractResult diff --git a/lib/Doctrine/Query/Registry.php b/lib/Doctrine/Query/Registry.php index 8cffec318..c89d7cdf0 100644 --- a/lib/Doctrine/Query/Registry.php +++ b/lib/Doctrine/Query/Registry.php @@ -28,7 +28,8 @@ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 1.0 - * @version $Revision$ + * @version $Revision$ + * @todo Remove */ class Doctrine_Query_Registry { diff --git a/lib/Doctrine/Query/Registry/Exception.php b/lib/Doctrine/Query/Registry/Exception.php index e138bdfa6..33e31d5bd 100644 --- a/lib/Doctrine/Query/Registry/Exception.php +++ b/lib/Doctrine/Query/Registry/Exception.php @@ -18,7 +18,7 @@ * and is licensed under the LGPL. For more information, see * . */ -Doctrine::autoload('Doctrine_Query_Exception'); + /** * Doctrine_Query_Exception * @@ -28,7 +28,8 @@ Doctrine::autoload('Doctrine_Query_Exception'); * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @link www.phpdoctrine.org * @since 1.0 - * @version $Revision$ + * @version $Revision$ + * @todo Remove */ class Doctrine_Query_Registry_Exception extends Doctrine_Query_Exception { } \ No newline at end of file diff --git a/lib/Doctrine/Query/Scanner.php b/lib/Doctrine/Query/Scanner.php index 634f0f47c..d14c4a102 100644 --- a/lib/Doctrine/Query/Scanner.php +++ b/lib/Doctrine/Query/Scanner.php @@ -102,9 +102,7 @@ class Doctrine_Query_Scanner foreach ($matches as $match) { $value = $match[0]; - $type = $this->_getType($value); - $this->_tokens[] = array( 'value' => $value, 'type' => $type, @@ -130,7 +128,6 @@ class Doctrine_Query_Scanner } else { $type = Doctrine_Query_Token::T_INTEGER; } - } if ($value[0] === "'" && $value[strlen($value) - 1] === "'") { $type = Doctrine_Query_Token::T_STRING; diff --git a/lib/Doctrine/Query/SqlBuilder.php b/lib/Doctrine/Query/SqlBuilder.php index b8f2c75e3..c664f5f73 100755 --- a/lib/Doctrine/Query/SqlBuilder.php +++ b/lib/Doctrine/Query/SqlBuilder.php @@ -31,7 +31,6 @@ * @link http://www.phpdoctrine.org * @since 2.0 * @version $Revision$ - * @todo Merge into DatabasePlatform. */ abstract class Doctrine_Query_SqlBuilder { diff --git a/lib/Doctrine/Query/Tokenizer.php b/lib/Doctrine/Query/Tokenizer.php index 5267ab52e..a68d77c4f 100644 --- a/lib/Doctrine/Query/Tokenizer.php +++ b/lib/Doctrine/Query/Tokenizer.php @@ -28,10 +28,7 @@ * @link www.phpdoctrine.org * @since 1.0 * @version $Revision$ - * @todo Give the tokenizer state, make it better work together with Doctrine_Query and maybe - * take out commonly used string manipulation methods - * into a stateless StringUtil? class. This tokenizer should be concerned with tokenizing - * DQL strings. + * @todo Remove. */ class Doctrine_Query_Tokenizer { diff --git a/lib/Doctrine/Query/Tokenizer/Exception.php b/lib/Doctrine/Query/Tokenizer/Exception.php index c569d5b59..6a936d72a 100644 --- a/lib/Doctrine/Query/Tokenizer/Exception.php +++ b/lib/Doctrine/Query/Tokenizer/Exception.php @@ -18,7 +18,7 @@ * and is licensed under the LGPL. For more information, see * . */ -Doctrine::autoload('Doctrine_Exception'); + /** * Doctrine_Query_Exception * @@ -29,6 +29,7 @@ Doctrine::autoload('Doctrine_Exception'); * @since 1.0 * @version $Revision: 2702 $ * @author Konsta Vesterinen + * @todo Remove */ class Doctrine_Query_Tokenizer_Exception extends Doctrine_Exception { } \ No newline at end of file diff --git a/lib/Doctrine/Schema/MsSqlSchemaManager.php b/lib/Doctrine/Schema/MsSqlSchemaManager.php index 08d72a6a3..42500d8c8 100644 --- a/lib/Doctrine/Schema/MsSqlSchemaManager.php +++ b/lib/Doctrine/Schema/MsSqlSchemaManager.php @@ -299,7 +299,7 @@ class Doctrine_Schema_MsSqlSchemaManager extends Doctrine_Schema_SchemaManager $val['type'] = $type; $val['identity'] = $identity; - $decl = $this->conn->dataDict->getPortableDeclaration($val); + $decl = $this->conn->getDatabasePlatform()->getPortableDeclaration($val); $description = array( 'name' => $val['column_name'], diff --git a/lib/Doctrine/Schema/MySqlSchemaManger.php b/lib/Doctrine/Schema/MySqlSchemaManger.php index 17d959867..27c59d450 100644 --- a/lib/Doctrine/Schema/MySqlSchemaManger.php +++ b/lib/Doctrine/Schema/MySqlSchemaManger.php @@ -32,14 +32,14 @@ */ class Doctrine_Schema_MySqlSchemaManager extends Doctrine_Schema_SchemaManager { - protected $_sql = array( + /*protected $_sql = array( 'showDatabases' => 'SHOW DATABASES', 'listTableFields' => 'DESCRIBE %s', 'listSequences' => 'SHOW TABLES', 'listTables' => 'SHOW TABLES', 'listUsers' => 'SELECT DISTINCT USER FROM USER', 'listViews' => "SHOW FULL TABLES %s WHERE Table_type = 'VIEW'", - ); + );*/ public function __construct(Doctrine_Connection_MySql $conn) { @@ -150,7 +150,7 @@ class Doctrine_Schema_MySqlSchemaManager extends Doctrine_Schema_SchemaManager $val = array_change_key_case($val, CASE_LOWER); - $decl = $this->_conn->dataDict->getPortableDeclaration($val); + $decl = $this->_conn->getDatabasePlatform()->getPortableDeclaration($val); $values = isset($decl['values']) ? $decl['values'] : array(); @@ -218,7 +218,8 @@ class Doctrine_Schema_MySqlSchemaManager extends Doctrine_Schema_SchemaManager */ public function listTables($database = null) { - return $this->_conn->fetchColumn($this->sql['listTables']); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListTablesSql()); } /** @@ -236,346 +237,6 @@ class Doctrine_Schema_MySqlSchemaManager extends Doctrine_Schema_SchemaManager return $this->_conn->fetchColumn($query); } - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @return string - * @override - */ - public function createDatabaseSql($name) - { - return 'CREATE DATABASE ' . $this->_conn->quoteIdentifier($name, true); - } - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @return string - * @override - */ - public function dropDatabaseSql($name) - { - return 'DROP DATABASE ' . $this->_conn->quoteIdentifier($name); - } - - /** - * 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: - * array( - * 'comment' => 'Foo', - * 'charset' => 'utf8', - * 'collate' => 'utf8_unicode_ci', - * 'type' => 'innodb', - * ); - * - * @return void - * @override - */ - 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); - - // build indexes for all foreign key fields (needed in MySQL!!) - if (isset($options['foreignKeys'])) { - foreach ($options['foreignKeys'] as $fk) { - $local = $fk['local']; - $found = false; - if (isset($options['indexes'])) { - foreach ($options['indexes'] as $definition) { - if (is_string($definition['fields'])) { - // Check if index already exists on the column - $found = ($local == $definition['fields']); - } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) { - // Index already exists on the column - $found = true; - } - } - } - if (isset($options['primary']) && !empty($options['primary']) && - in_array($local, $options['primary'])) { - // field is part of the PK and therefore already indexed - $found = true; - } - - if ( ! $found) { - $options['indexes'][$local] = array('fields' => array($local => array())); - } - } - } - - // add all indexes - if (isset($options['indexes']) && ! empty($options['indexes'])) { - foreach($options['indexes'] as $index => $definition) { - $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition); - } - } - - // attach all primary keys - if (isset($options['primary']) && ! empty($options['primary'])) { - $keyColumns = array_values($options['primary']); - $keyColumns = array_map(array($this->_conn, 'quoteIdentifier'), $keyColumns); - $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; - } - - $query = 'CREATE '; - if (!empty($options['temporary'])) { - $query .= 'TEMPORARY '; - } - $query.= 'TABLE ' . $this->_conn->quoteIdentifier($name, true) . ' (' . $queryFields . ')'; - - $optionStrings = array(); - - if (isset($options['comment'])) { - $optionStrings['comment'] = 'COMMENT = ' . $this->dbh->quote($options['comment'], 'text'); - } - if (isset($options['charset'])) { - $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset']; - if (isset($options['collate'])) { - $optionStrings['charset'] .= ' COLLATE ' . $options['collate']; - } - } - - $type = false; - - // get the type of the table - if (isset($options['type'])) { - $type = $options['type']; - } else { - $type = $this->_conn->getAttribute(Doctrine::ATTR_DEFAULT_TABLE_TYPE); - } - - if ($type) { - $optionStrings[] = 'ENGINE = ' . $type; - } - - if ( ! empty($optionStrings)) { - $query.= ' '.implode(' ', $optionStrings); - } - $sql[] = $query; - - if (isset($options['foreignKeys'])) { - - foreach ((array) $options['foreignKeys'] as $k => $definition) { - if (is_array($definition)) { - $sql[] = $this->createForeignKeySql($name, $definition); - } - } - } - return $sql; - } - - /** - * alter an existing table - * - * @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 Metabase 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 Metabase 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 boolean - * @override - */ - public function alterTableSql($name, array $changes, $check = false) - { - if ( ! $name) { - throw new Doctrine_Export_Exception('no valid table name specified'); - } - foreach ($changes as $changeName => $change) { - switch ($changeName) { - case 'add': - case 'remove': - case 'change': - case 'rename': - case 'name': - break; - default: - throw new Doctrine_Export_Exception('change type "' . $changeName . '" not yet supported'); - } - } - - if ($check) { - return true; - } - - $query = ''; - if ( ! empty($changes['name'])) { - $change_name = $this->_conn->quoteIdentifier($changes['name']); - $query .= 'RENAME TO ' . $change_name; - } - - if ( ! empty($changes['add']) && is_array($changes['add'])) { - foreach ($changes['add'] as $fieldName => $field) { - if ($query) { - $query.= ', '; - } - $query.= 'ADD ' . $this->getDeclaration($fieldName, $field); - } - } - - if ( ! empty($changes['remove']) && is_array($changes['remove'])) { - foreach ($changes['remove'] as $fieldName => $field) { - if ($query) { - $query .= ', '; - } - $fieldName = $this->_conn->quoteIdentifier($fieldName); - $query .= 'DROP ' . $fieldName; - } - } - - $rename = array(); - if ( ! empty($changes['rename']) && is_array($changes['rename'])) { - foreach ($changes['rename'] as $fieldName => $field) { - $rename[$field['name']] = $fieldName; - } - } - - if ( ! empty($changes['change']) && is_array($changes['change'])) { - foreach ($changes['change'] as $fieldName => $field) { - if ($query) { - $query.= ', '; - } - if (isset($rename[$fieldName])) { - $oldFieldName = $rename[$fieldName]; - unset($rename[$fieldName]); - } else { - $oldFieldName = $fieldName; - } - $oldFieldName = $this->_conn->quoteIdentifier($oldFieldName, true); - $query .= 'CHANGE ' . $oldFieldName . ' ' - . $this->getDeclaration($fieldName, $field['definition']); - } - } - - if ( ! empty($rename) && is_array($rename)) { - foreach ($rename as $renameName => $renamedField) { - if ($query) { - $query.= ', '; - } - $field = $changes['rename'][$renamedField]; - $renamedField = $this->_conn->quoteIdentifier($renamedField, true); - $query .= 'CHANGE ' . $renamedField . ' ' - . $this->getDeclaration($field['name'], $field['definition']); - } - } - - if ( ! $query) { - return false; - } - - $name = $this->_conn->quoteIdentifier($name, true); - - return 'ALTER TABLE ' . $name . ' ' . $query; - } /** * create sequence @@ -654,287 +315,6 @@ class Doctrine_Schema_MySqlSchemaManager extends Doctrine_Schema_SchemaManager return $res; } - - /** - * Get the stucture of a field into an array - * - * @author Leoncx - * @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' => 'ASC' - * 'length' => 10 - * ), - * 'last_login' => array() - * ) - * ) - * @throws PDOException - * @return void - * @override - */ - public function createIndexSql($table, $name, array $definition) - { - $table = $table; - $name = $this->_conn->formatter->getIndexName($name); - $name = $this->_conn->quoteIdentifier($name); - $type = ''; - if (isset($definition['type'])) { - switch (strtolower($definition['type'])) { - case 'fulltext': - 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; - $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')'; - - return $query; - } - - /** - * 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 string $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. - * @override - */ - public function getIntegerDeclaration($name, $field) - { - $default = $autoinc = ''; - if ( ! empty($field['autoincrement'])) { - $autoinc = ' AUTO_INCREMENT'; - } elseif (array_key_exists('default', $field)) { - if ($field['default'] === '') { - $field['default'] = empty($field['notnull']) ? null : 0; - } - if (is_null($field['default'])) { - $default = ' DEFAULT NULL'; - } else { - $default = ' DEFAULT '.$this->_conn->quote($field['default']); - } - } elseif (empty($field['notnull'])) { - $default = ' DEFAULT NULL'; - } - - $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; - $unsigned = (isset($field['unsigned']) && $field['unsigned']) ? ' UNSIGNED' : ''; - - $name = $this->_conn->quoteIdentifier($name, true); - - return $name . ' ' . $this->_conn->dataDict->getNativeDeclaration($field) . $unsigned . $default . $notnull . $autoinc; - } - - /** - * 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 - * @override - */ - public function getDefaultFieldDeclaration($field) - { - $default = empty($field['notnull']) && !in_array($field['type'], array('clob', 'blob')) - ? ' DEFAULT NULL' : ''; - - if (isset($field['default']) && ( ! isset($field['length']) || $field['length'] <= 255)) { - if ($field['default'] === '') { - $field['default'] = null; - if (! empty($field['notnull']) && array_key_exists($field['type'], $this->valid_default_values)) { - $field['default'] = $this->valid_default_values[$field['type']]; - } - - if ($field['default'] === '' - && ($this->_conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL) - ) { - $field['default'] = ' '; - } - } - - if ($field['type'] == 'enum' && $this->_conn->getAttribute(Doctrine::ATTR_USE_NATIVE_ENUM)) { - $fieldType = 'varchar'; - } else { - if ($field['type'] === 'boolean') { - $fields['default'] = $this->_conn->convertBooleans($field['default']); - } - $fieldType = $field['type']; - } - - $default = ' DEFAULT ' . $this->_conn->quote($field['default'], $fieldType); - } - 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 - * @override - */ - public function getIndexDeclaration($name, array $definition) - { - $name = $this->_conn->formatter->getIndexName($name); - $type = ''; - if (isset($definition['type'])) { - switch (strtolower($definition['type'])) { - case 'fulltext': - case 'unique': - $type = strtoupper($definition['type']) . ' '; - break; - default: - throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']); - } - } - - if ( ! isset($definition['fields'])) { - throw new Doctrine_Export_Exception('No index columns given.'); - } - if ( ! is_array($definition['fields'])) { - $definition['fields'] = array($definition['fields']); - } - - $query = $type . 'INDEX ' . $this->_conn->quoteIdentifier($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 - * @override - */ - public function getIndexFieldDeclarationList(array $fields) - { - $declFields = array(); - - foreach ($fields as $fieldName => $field) { - $fieldString = $this->_conn->quoteIdentifier($fieldName); - - if (is_array($field)) { - if (isset($field['length'])) { - $fieldString .= '(' . $field['length'] . ')'; - } - - if (isset($field['sorting'])) { - $sort = strtoupper($field['sorting']); - switch ($sort) { - case 'ASC': - case 'DESC': - $fieldString .= ' ' . $sort; - break; - default: - throw new Doctrine_Export_Exception('Unknown index sorting option given.'); - } - } - } else { - $fieldString = $this->_conn->quoteIdentifier($field); - } - $declFields[] = $fieldString; - } - return implode(', ', $declFields); - } - - /** - * getAdvancedForeignKeyOptions - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition - * @return string - * @override - */ - public function getAdvancedForeignKeyOptions(array $definition) - { - $query = ''; - if ( ! empty($definition['match'])) { - $query .= ' MATCH ' . $definition['match']; - } - if ( ! empty($definition['onUpdate'])) { - $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']); - } - if ( ! empty($definition['onDelete'])) { - $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']); - } - return $query; - } - - /** - * 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 - * @override - */ - public function dropIndexSql($table, $name) - { - $table = $this->_conn->quoteIdentifier($table, true); - $name = $this->_conn->quoteIdentifier($this->_conn->formatter->getIndexName($name), true); - return 'DROP INDEX ' . $name . ' ON ' . $table; - } - - /** - * dropTable - * - * @param string $table name of table that should be dropped from the database - * @throws PDOException - * @return void - * @override - */ - public function dropTableSql($table) - { - $table = $this->_conn->quoteIdentifier($table, true); - return 'DROP TABLE ' . $table; - } /** * Enter description here... diff --git a/lib/Doctrine/Schema/PostgreSqlSchemaManager.php b/lib/Doctrine/Schema/PostgreSqlSchemaManager.php index c1f0ec7af..a47c15144 100644 --- a/lib/Doctrine/Schema/PostgreSqlSchemaManager.php +++ b/lib/Doctrine/Schema/PostgreSqlSchemaManager.php @@ -31,246 +31,16 @@ * @since 2.0 */ class Doctrine_Schema_PostgreSqlSchemaManager extends Doctrine_Schema_SchemaManager -{ - protected $sql = array( - 'listDatabases' => 'SELECT datname FROM pg_database', - 'listFunctions' => "SELECT - proname - FROM - pg_proc pr, - pg_type tp - WHERE - tp.oid = pr.prorettype - AND pr.proisagg = FALSE - AND tp.typname <> 'trigger' - AND pr.pronamespace IN - (SELECT oid FROM pg_namespace - WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema'", - 'listSequences' => "SELECT - relname - FROM - pg_class - WHERE relkind = 'S' AND relnamespace IN - (SELECT oid FROM pg_namespace - WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')", - 'listTables' => "SELECT - c.relname AS table_name - FROM pg_class c, pg_user u - WHERE c.relowner = u.usesysid - AND c.relkind = 'r' - AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) - AND c.relname !~ '^(pg_|sql_)' - UNION - SELECT c.relname AS table_name - FROM pg_class c - WHERE c.relkind = 'r' - AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) - AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) - AND c.relname !~ '^pg_'", - 'listViews' => 'SELECT viewname FROM pg_views', - 'listUsers' => 'SELECT usename FROM pg_user', - 'listTableConstraints' => "SELECT - relname - FROM - pg_class - WHERE oid IN ( - SELECT indexrelid - FROM pg_index, pg_class - WHERE pg_class.relname = %s - AND pg_class.oid = pg_index.indrelid - AND (indisunique = 't' OR indisprimary = 't') - )", - 'listTableIndexes' => "SELECT - relname - FROM - pg_class - WHERE oid IN ( - SELECT indexrelid - FROM pg_index, pg_class - WHERE pg_class.relname = %s - AND pg_class.oid=pg_index.indrelid - AND indisunique != 't' - AND indisprimary != 't' - )", - 'listTableColumns' => "SELECT - a.attnum, - a.attname AS field, - t.typname AS type, - format_type(a.atttypid, a.atttypmod) AS complete_type, - a.attnotnull AS isnotnull, - (SELECT 't' - FROM pg_index - WHERE c.oid = pg_index.indrelid - AND pg_index.indkey[0] = a.attnum - AND pg_index.indisprimary = 't' - ) AS pri, - (SELECT pg_attrdef.adsrc - FROM pg_attrdef - WHERE c.oid = pg_attrdef.adrelid - AND pg_attrdef.adnum=a.attnum - ) AS default - FROM pg_attribute a, pg_class c, pg_type t - WHERE c.relname = %s - AND a.attnum > 0 - AND a.attrelid = c.oid - AND a.atttypid = t.oid - ORDER BY a.attnum", - ); - - - +{ + /** + * Enter description here... + * + * @param Doctrine_Connection_Pgsql $conn + */ public function __construct(Doctrine_Connection_Pgsql $conn) { $this->_conn = $conn; } - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @throws PDOException - * @return void - */ - public function createDatabaseSql($name) - { - $query = 'CREATE DATABASE ' . $this->_conn->quoteIdentifier($name); - - return $query; - } - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @throws PDOException - * @access public - */ - public function dropDatabaseSql($name) - { - $query = 'DROP DATABASE ' . $this->_conn->quoteIdentifier($name); - - return $query; - } - - /** - * 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 - * @access protected - */ - public function getAdvancedForeignKeyOptions(array $definition) - { - $query = ''; - if (isset($definition['match'])) { - $query .= ' MATCH ' . $definition['match']; - } - if (isset($definition['onUpdate'])) { - $query .= ' ON UPDATE ' . $definition['onUpdate']; - } - if (isset($definition['onDelete'])) { - $query .= ' ON DELETE ' . $definition['onDelete']; - } - if (isset($definition['deferrable'])) { - $query .= ' DEFERRABLE'; - } else { - $query .= ' NOT DEFERRABLE'; - } - if (isset($definition['feferred'])) { - $query .= ' INITIALLY DEFERRED'; - } else { - $query .= ' INITIALLY IMMEDIATE'; - } - return $query; - } - - /** - * generates the sql for altering an existing table on postgresql - * - * @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 array - */ - public function alterTableSql($name, array $changes, $check = false) - { - foreach ($changes as $changeName => $change) { - switch ($changeName) { - case 'add': - case 'remove': - case 'change': - case 'name': - case 'rename': - break; - default: - throw new Doctrine_Export_Exception('change type "' . $changeName . '\" not yet supported'); - } - } - - if ($check) { - return true; - } - - $sql = array(); - - if (isset($changes['add']) && is_array($changes['add'])) { - foreach ($changes['add'] as $fieldName => $field) { - $query = 'ADD ' . $this->getDeclaration($fieldName, $field); - $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; - } - } - - if (isset($changes['remove']) && is_array($changes['remove'])) { - foreach ($changes['remove'] as $fieldName => $field) { - $fieldName = $this->_conn->quoteIdentifier($fieldName, true); - $query = 'DROP ' . $fieldName; - $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; - } - } - - if (isset($changes['change']) && is_array($changes['change'])) { - foreach ($changes['change'] as $fieldName => $field) { - $fieldName = $this->_conn->quoteIdentifier($fieldName, true); - if (isset($field['type'])) { - $serverInfo = $this->_conn->getServerVersion(); - - if (is_array($serverInfo) && $serverInfo['major'] < 8) { - throw new Doctrine_Export_Exception('changing column type for "'.$field['type'].'\" requires PostgreSQL 8.0 or above'); - } - $query = 'ALTER ' . $fieldName . ' TYPE ' . $this->_conn->datatype->getTypeDeclaration($field['definition']); - $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; - } - if (array_key_exists('default', $field)) { - $query = 'ALTER ' . $fieldName . ' SET DEFAULT ' . $this->_conn->quote($field['definition']['default'], $field['definition']['type']); - $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; - } - if ( ! empty($field['notnull'])) { - $query = 'ALTER ' . $fieldName . ' ' . ($field['definition']['notnull'] ? 'SET' : 'DROP') . ' NOT NULL'; - $sql[] = 'ALTER TABLE ' . $name . ' ' . $query; - } - } - } - - if (isset($changes['rename']) && is_array($changes['rename'])) { - foreach ($changes['rename'] as $fieldName => $field) { - $fieldName = $this->_conn->quoteIdentifier($fieldName, true); - $sql[] = 'ALTER TABLE ' . $name . ' RENAME COLUMN ' . $fieldName . ' TO ' . $this->_conn->quoteIdentifier($field['name'], true); - } - } - - $name = $this->_conn->quoteIdentifier($name, true); - if (isset($changes['name'])) { - $changeName = $this->_conn->quoteIdentifier($changes['name'], true); - $sql[] = 'ALTER TABLE ' . $name . ' RENAME TO ' . $changeName; - } - - return $sql; - } /** * alter an existing table @@ -369,137 +139,6 @@ class Doctrine_Schema_PostgreSqlSchemaManager extends Doctrine_Schema_SchemaMana } return true; } - - /** - * return RDBMS specific create sequence statement - * - * @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($sequenceName, $start = 1, array $options = array()) - { - $sequenceName = $this->_conn->quoteIdentifier($this->_conn->formatter->getSequenceName($sequenceName), true); - return $this->_conn->exec('CREATE SEQUENCE ' . $sequenceName . ' INCREMENT 1' . - ($start < 1 ? ' MINVALUE ' . $start : '') . ' START ' . $start); - } - - /** - * drop existing sequence - * - * @param string $sequenceName name of the sequence to be dropped - */ - public function dropSequenceSql($sequenceName) - { - $sequenceName = $this->_conn->quoteIdentifier($this->_conn->formatter->getSequenceName($sequenceName), true); - 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'])) { - $keyColumns = array_values($options['primary']); - $keyColumns = array_map(array($this->_conn, 'quoteIdentifier'), $keyColumns); - $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; - } - - $query = 'CREATE TABLE ' . $this->_conn->quoteIdentifier($name, true) . ' (' . $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; - } - - /** - * 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->_conn->dataDict->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']); - } elseif (empty($field['notnull'])) { - $default = ' DEFAULT NULL'; - } - - $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; - $name = $this->_conn->quoteIdentifier($name, true); - return $name . ' ' . $this->_conn->dataDict->getNativeDeclaration($field) . $default . $notnull; - } /** * lists all database triggers @@ -548,7 +187,7 @@ class Doctrine_Schema_PostgreSqlSchemaManager extends Doctrine_Schema_SchemaMana $val['length'] = $length; } - $decl = $this->_conn->dataDict->getPortableDeclaration($val); + $decl = $this->_conn->getDatabasePlatform()->getPortableDeclaration($val); $description = array( 'name' => $val['field'], diff --git a/lib/Doctrine/Schema/SchemaManager.php b/lib/Doctrine/Schema/SchemaManager.php index 3f1e7e8fd..f84bccf7d 100644 --- a/lib/Doctrine/Schema/SchemaManager.php +++ b/lib/Doctrine/Schema/SchemaManager.php @@ -22,31 +22,19 @@ #namespace Doctrine::DBAL::Schema; /** - * xxx + * Base class for schema managers. Schema managers are used to inspect and/or + * modify the database schema. * * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @author Konsta Vesterinen * @author Lukas Smith (PEAR MDB2 library) * @version $Revision$ * @since 2.0 + * @todo Rename to AbstractSchemaManager */ abstract class Doctrine_Schema_SchemaManager { protected $_conn; - protected $sql = array(); - 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' => '' - ); /** * lists all databases @@ -55,11 +43,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listDatabases() { - if ( ! isset($this->sql['listDatabases'])) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); - } - - return $this->_conn->fetchColumn($this->sql['listDatabases']); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListDatabasesSql()); } /** @@ -69,11 +54,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listFunctions() { - if ( ! isset($this->sql['listFunctions'])) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); - } - - return $this->_conn->fetchColumn($this->sql['listFunctions']); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListFunctionsSql()); } /** @@ -84,7 +66,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listTriggers($database = null) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListTriggersSql()); } /** @@ -95,11 +78,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listSequences($database = null) { - if ( ! isset($this->_sql['listSequences'])) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); - } - - return $this->_conn->fetchColumn($this->sql['listSequences']); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListSequencesSql()); } /** @@ -110,7 +90,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listTableConstraints($table) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListTableConstraintsSql()); } /** @@ -121,7 +102,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listTableColumns($table) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListTableColumnsSql()); } /** @@ -132,7 +114,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listTableIndexes($table) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListTableIndexesSql()); } /** @@ -143,29 +126,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listTables($database = null) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); - } - - /** - * Lists table triggers. - * - * @param string $table database table name - * @return array - */ - public function listTableTriggers($table) - { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); - } - - /** - * Lists table views. - * - * @param string $table database table name - * @return array - */ - public function listTableViews($table) - { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListTablesSql()); } /** @@ -175,11 +137,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listUsers() { - if ( ! isset($this->_sql['listUsers'])) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); - } - - return $this->_conn->fetchColumn($this->sql['listUsers']); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListUsersSql()); } /** @@ -190,11 +149,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function listViews($database = null) { - if ( ! isset($this->_sql['listViews'])) { - throw new Doctrine_Import_Exception(__FUNCTION__ . ' not supported by this driver.'); - } - - return $this->_conn->fetchColumn($this->sql['listViews']); + return $this->_conn->fetchColumn($this->_conn->getDatabasePlatform() + ->getListViewsSql()); } /** @@ -206,31 +162,8 @@ abstract class Doctrine_Schema_SchemaManager */ 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); + $this->_conn->execute($this->_conn->getDatabasePlatform() + ->getDropDatabaseSql($database)); } /** @@ -242,7 +175,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function dropTable($table) { - $this->_conn->execute($this->dropTableSql($table)); + $this->_conn->execute($this->_conn->getDatabasePlatform() + ->getDropTableSql($table)); } /** @@ -254,21 +188,8 @@ abstract class Doctrine_Schema_SchemaManager */ 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; + return $this->_conn->exec($this->_conn->getDatabasePlatform() + ->getDropIndexSql($table, $name)); } /** @@ -281,8 +202,8 @@ abstract class Doctrine_Schema_SchemaManager */ public function dropConstraint($table, $name, $primary = false) { - $table = $this->_conn->quoteIdentifier($table); - $name = $this->_conn->quoteIdentifier($name); + $table = $this->_conn->getDatabasePlatform()->quoteIdentifier($table); + $name = $this->_conn->getDatabasePlatform()->quoteIdentifier($name); return $this->_conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name); } @@ -300,7 +221,6 @@ abstract class Doctrine_Schema_SchemaManager } /** - * dropSequenceSql * drop existing sequence * (this method is implemented by the drivers) * @@ -310,20 +230,7 @@ abstract class Doctrine_Schema_SchemaManager */ 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.'); + $this->_conn->exec($this->_conn->getDatabasePlatform()->getDropSequenceSql($sequenceName)); } /** @@ -335,93 +242,7 @@ abstract class Doctrine_Schema_SchemaManager */ 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); - } - } - - $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name, true) . ' (' . $queryFields; - - $check = $this->getCheckDeclaration($fields); - - if ( ! empty($check)) { - $query .= ', ' . $check; - } - - $query .= ')'; - - $sql[] = $query; - - if (isset($options['foreignKeys'])) { - - foreach ((array) $options['foreignKeys'] as $k => $definition) { - if (is_array($definition)) { - $sql[] = $this->createForeignKeySql($name, $definition); - } - } - } - return $sql; + $this->_conn->execute($this->_conn->getDatabasePlatform()->getCreateDatabaseSql($database)); } /** @@ -434,22 +255,23 @@ abstract class Doctrine_Schema_SchemaManager * * @return void */ - public function createTable($name, array $fields, array $options = array()) + public function createTable($name, array $columns, array $options = array()) { // Build array of the primary keys if any of the individual field definitions // specify primary => true $count = 0; - foreach ($fields as $fieldName => $field) { - if (isset($field['primary']) && $field['primary']) { + foreach ($columns as $columnName => $definition) { + if (isset($definition['primary']) && $definition['primary']) { if ($count == 0) { $options['primary'] = array(); } $count++; - $options['primary'][] = $fieldName; + $options['primary'][] = $columnName; } } - $sql = (array) $this->createTableSql($name, $fields, $options); + $sql = (array) $this->_conn->getDatabasePlatform()->getCreateTableSql( + $name, $columns, $options); foreach ($sql as $query) { $this->_conn->execute($query); @@ -472,27 +294,8 @@ abstract class Doctrine_Schema_SchemaManager */ 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.'); + return $this->_conn->execute($this->_conn->getDatabasePlatform() + ->getCreateSequenceSql($seqName, $start, $options)); } /** @@ -518,53 +321,10 @@ abstract class Doctrine_Schema_SchemaManager */ public function createConstraint($table, $name, $definition) { - $sql = $this->createConstraintSql($table, $name, $definition); - + $sql = $this->_conn->getDatabasePlatform()->getCreateConstraintSql($table, $name, $definition); return $this->_conn->exec($sql); } - /** - * 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 * @@ -598,58 +358,8 @@ abstract class Doctrine_Schema_SchemaManager */ 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 ' . $this->getForeignKeyDeclaration($definition); - - return $query; + return $this->_conn->execute($this->_conn->getDatabasePlatform() + ->getCreateIndexSql($table, $name, $definition)); } /** @@ -661,8 +371,7 @@ abstract class Doctrine_Schema_SchemaManager */ public function createForeignKey($table, array $definition) { - $sql = $this->createForeignKeySql($table, $definition); - + $sql = $this->_conn->getDatabasePlatform()->getCreateForeignKeySql($table, $definition); return $this->_conn->execute($sql); } @@ -757,433 +466,12 @@ abstract class Doctrine_Schema_SchemaManager */ public function alterTable($name, array $changes, $check = false) { - $sql = $this->alterTableSql($name, $changes, $check); + $sql = $this->_conn->getDatabasePlatform()->getAlterTableSql($name, $changes, $check); if (is_string($sql) && $sql) { $this->_conn->execute($sql); } } - - /** - * 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 = false) - { - 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') { - $field['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 a CHECK constraint - * declaration to be used in statements like CREATE TABLE. - * - * @param array $definition check definition - * @return string DBMS specific SQL code portion needed to set a CHECK constraint - */ - public function getCheckDeclaration(array $definition) - { - $constraints = array(); - foreach ($definition as $field => $def) { - if (is_string($def)) { - $constraints[] = 'CHECK (' . $def . ')'; - } else { - if (isset($def['min'])) { - $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')'; - } - - if (isset($def['max'])) { - $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')'; - } - } - } - - return implode(', ', $constraints); - } - - /** - * Obtain DBMS specific SQL code portion needed to set an index - * declaration to be used in statements like CREATE TABLE. - * - * @param string $name 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->getForeignKeyReferentialAction($definition['onUpdate']); - } - if ( ! empty($definition['onDelete'])) { - $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($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 ' - . $this->_conn->quoteIdentifier($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 ''; - } - } ?> \ No newline at end of file diff --git a/query-language.txt b/query-language.txt index 4762e9de8..a45bd6d37 100644 --- a/query-language.txt +++ b/query-language.txt @@ -5,7 +5,7 @@ * - terminals begin with a lower case character * - parentheses (...) are used for grouping * - square brackets [...] are used for defining an optional part, eg. zero or - * one time time + * one time * - curly brackets {...} are used for repetion, eg. zero or more times * - double quotation marks "..." define a terminal string * - a vertical bar | represents an alternative @@ -14,69 +14,69 @@ * Initially Select and Sub-select DQL will not support LIMIT and OFFSET (due to limit-subquery algorithm) */ -QueryLanguage = SelectStatement | UpdateStatement | DeleteStatement +QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement -SelectStatement = SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] -UpdateStatement = UpdateClause [WhereClause] -DeleteStatement = DeleteClause [WhereClause] +SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] +UpdateStatement ::= UpdateClause [WhereClause] +DeleteStatement ::= DeleteClause [WhereClause] -Subselect = SimpleSelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] -SelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression} -SimpleSelectClause = "SELECT" ["ALL" | "DISTINCT"] SelectExpression -DeleteClause = "DELETE" ["FROM"] VariableDeclaration -WhereClause = "WHERE" ConditionalExpression -FromClause = "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration} -HavingClause = "HAVING" ConditionalExpression -GroupByClause = "GROUP" "BY" GroupByItem {"," GroupByItem} -OrderByClause = "ORDER" "BY" OrderByItem {"," OrderByItem} -LimitClause = "LIMIT" integer -OffsetClause = "OFFSET" integer -UpdateClause = "UPDATE" VariableDeclaration "SET" UpdateItem {"," UpdateItem} +Subselect ::= SimpleSelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] +SelectClause ::= "SELECT" ["ALL" | "DISTINCT"] SelectExpression {"," SelectExpression}* +SimpleSelectClause ::= "SELECT" ["ALL" | "DISTINCT"] SelectExpression +DeleteClause ::= "DELETE" ["FROM"] VariableDeclaration +WhereClause ::= "WHERE" ConditionalExpression +FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* +HavingClause ::= "HAVING" ConditionalExpression +GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* +OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* +LimitClause ::= "LIMIT" integer +OffsetClause ::= "OFFSET" integer +UpdateClause ::= "UPDATE" VariableDeclaration "SET" UpdateItem {"," UpdateItem}* -OrderByItem = Expression ["ASC" | "DESC"] -GroupByItem = PathExpression -UpdateItem = PathExpression "=" (Expression | "NULL") +OrderByItem ::= Expression ["ASC" | "DESC"] +GroupByItem ::= PathExpression +UpdateItem ::= PathExpression "=" (Expression | "NULL") -IdentificationVariableDeclaration = RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration} -JoinVariableDeclaration = Join [IndexBy] -RangeVariableDeclaration = identifier {"." identifier} [["AS"] IdentificationVariable] -VariableDeclaration = identifier [["AS"] IdentificationVariable] -IdentificationVariable = identifier +IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* +JoinVariableDeclaration ::= Join [IndexBy] +RangeVariableDeclaration ::= identifier {"." identifier}* [["AS"] IdentificationVariable] +VariableDeclaration ::= identifier [["AS"] IdentificationVariable] +IdentificationVariable ::= identifier -Join = ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression] -IndexBy = "INDEX" "BY" identifier +Join ::= ["LEFT" | "INNER"] "JOIN" RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression] +IndexBy ::= "INDEX" "BY" identifier -ConditionalExpression = ConditionalTerm {"OR" ConditionalTerm} -ConditionalTerm = ConditionalFactor {"AND" ConditionalFactor} -ConditionalFactor = ["NOT"] ConditionalPrimary -ConditionalPrimary = SimpleConditionalExpression | "(" ConditionalExpression ")" +ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* +ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* +ConditionalFactor ::= ["NOT"] ConditionalPrimary +ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" SimpleConditionalExpression - = Expression (ComparisonExpression | BetweenExpression | LikeExpression + ::= Expression (ComparisonExpression | BetweenExpression | LikeExpression | InExpression | NullComparisonExpression) | ExistsExpression -Atom = string | integer | float | boolean | input_parameter +Atom ::= string | integer | float | boolean | input_parameter -Expression = Term {("+" | "-") Term} -Term = Factor {("*" | "/") Factor} -Factor = [("+" | "-")] Primary -Primary = PathExpression | Atom | "(" Expression ")" | Function | AggregateExpression +Expression ::= Term {("+" | "-") Term}* +Term ::= Factor {("*" | "/") Factor}* +Factor ::= [("+" | "-")] Primary +Primary ::= PathExpression | Atom | "(" Expression ")" | Function | AggregateExpression -SelectExpression = (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")" ) [["AS"] FieldIdentificationVariable] -PathExpression = identifier {"." identifier} -PathExpressionEndingWithAsterisk = {identifier "."} "*" -FieldIdentificationVariable = identifier +SelectExpression ::= (PathExpressionEndingWithAsterisk | Expression | "(" Subselect ")" ) [["AS"] FieldIdentificationVariable] +PathExpression ::= identifier {"." identifier}* +PathExpressionEndingWithAsterisk ::= {identifier "."}* "*" +FieldIdentificationVariable ::= identifier -AggregateExpression = ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] Expression ")" +AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] Expression ")" | "COUNT" "(" ["DISTINCT"] (Expression | "*") ")" -QuantifiedExpression = ("ALL" | "ANY" | "SOME") "(" Subselect ")" -BetweenExpression = ["NOT"] "BETWEEN" Expression "AND" Expression -ComparisonExpression = ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" ) -InExpression = ["NOT"] "IN" "(" (Atom {"," Atom} | Subselect) ")" -LikeExpression = ["NOT"] "LIKE" Expression ["ESCAPE" string] -NullComparisonExpression = "IS" ["NOT"] "NULL" -ExistsExpression = "EXISTS" "(" Subselect ")" +QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" +BetweenExpression ::= ["NOT"] "BETWEEN" Expression "AND" Expression +ComparisonExpression ::= ComparisonOperator ( QuantifiedExpression | Expression | "(" Subselect ")" ) +InExpression ::= ["NOT"] "IN" "(" (Atom {"," Atom}* | Subselect) ")" +LikeExpression ::= ["NOT"] "LIKE" Expression ["ESCAPE" string] +NullComparisonExpression ::= "IS" ["NOT"] "NULL" +ExistsExpression ::= "EXISTS" "(" Subselect ")" -ComparisonOperator = "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" +ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" -Function = identifier "(" [Expression {"," Expression}] ")" +Function ::= identifier "(" [Expression {"," Expression}*] ")" diff --git a/tests/Orm/AllTests.php b/tests/Orm/AllTests.php index a212b6a68..dd18a37b7 100644 --- a/tests/Orm/AllTests.php +++ b/tests/Orm/AllTests.php @@ -11,6 +11,7 @@ require_once 'Orm/Query/AllTests.php'; require_once 'Orm/Hydration/AllTests.php'; require_once 'Orm/Ticket/AllTests.php'; require_once 'Orm/Entity/AllTests.php'; +require_once 'Orm/Associations/AllTests.php'; // Tests require_once 'Orm/UnitOfWorkTest.php'; @@ -39,6 +40,7 @@ class Orm_AllTests $suite->addTest(Orm_Hydration_AllTests::suite()); $suite->addTest(Orm_Entity_AllTests::suite()); $suite->addTest(Orm_Ticket_AllTests::suite()); + $suite->addTest(Orm_Associations_AllTests::suite()); return $suite; } diff --git a/tests/Orm/Associations/AllTests.php b/tests/Orm/Associations/AllTests.php new file mode 100644 index 000000000..dab2eb871 --- /dev/null +++ b/tests/Orm/Associations/AllTests.php @@ -0,0 +1,30 @@ +addTestSuite('Orm_Associations_OneToOneMappingTest'); + + return $suite; + } +} + +if (PHPUnit_MAIN_METHOD == 'Orm_Associations_AllTests::main') { + Orm_Associations_AllTests::main(); +} diff --git a/tests/Orm/Query/LanguageRecognitionTest.php b/tests/Orm/Query/LanguageRecognitionTest.php index 51a9cfbfd..eb6d6bcb4 100755 --- a/tests/Orm/Query/LanguageRecognitionTest.php +++ b/tests/Orm/Query/LanguageRecognitionTest.php @@ -38,18 +38,21 @@ require_once 'lib/DoctrineTestInit.php'; */ class Orm_Query_LanguageRecognitionTest extends Doctrine_OrmTestCase { - public function assertValidDql($dql) + public function assertValidDql($dql, $debug = false) { try { $entityManager = $this->_em; $query = $entityManager->createQuery($dql); $parserResult = $query->parse(); } catch (Doctrine_Exception $e) { + if ($debug) { + echo $e->getTraceAsString() . PHP_EOL; + } $this->fail($e->getMessage()); } } - public function assertInvalidDql($dql) + public function assertInvalidDql($dql, $debug = false) { try { $entityManager = $this->_em; @@ -59,6 +62,11 @@ class Orm_Query_LanguageRecognitionTest extends Doctrine_OrmTestCase $this->fail('No syntax errors were detected, when syntax errors were expected'); } catch (Doctrine_Exception $e) { + //echo $e->getMessage() . PHP_EOL; + if ($debug) { + echo $e->getMessage() . PHP_EOL; + echo $e->getTraceAsString() . PHP_EOL; + } // It was expected! } } @@ -351,20 +359,20 @@ class Orm_Query_LanguageRecognitionTest extends Doctrine_OrmTestCase { $this->assertValidDql("SELECT * FROM CmsUser u WHERE u.name NOT BETWEEN 'jepso' AND 'zYne'"); } -/* - public function testAllExpression() + +/* public function testAllExpressionWithCorrelatedSubquery() { // We need existant classes here, otherwise semantical will always fail - $this->assertValidDql('SELECT * FROM Employee e WHERE e.salary > ALL (SELECT m.salary FROM Manager m WHERE m.department = e.department)'); + $this->assertValidDql('SELECT * FROM CompanyEmployee e WHERE e.salary > ALL (SELECT m.salary FROM CompanyManager m WHERE m.department = e.department)', true); } - public function testAnyExpression() + public function testAnyExpressionWithCorrelatedSubquery() { // We need existant classes here, otherwise semantical will always fail $this->assertValidDql('SELECT * FROM Employee e WHERE e.salary > ANY (SELECT m.salary FROM Manager m WHERE m.department = e.department)'); } - public function testSomeExpression() + public function testSomeExpressionWithCorrelatedSubquery() { // We need existant classes here, otherwise semantical will always fail $this->assertValidDql('SELECT * FROM Employee e WHERE e.salary > SOME (SELECT m.salary FROM Manager m WHERE m.department = e.department)'); @@ -385,14 +393,47 @@ class Orm_Query_LanguageRecognitionTest extends Doctrine_OrmTestCase $this->assertValidDql("SELECT u.id FROM CmsUser u WHERE u.name LIKE 'z|%' ESCAPE '|'"); } + /** + * TODO: Hydration can't deal with this currently but it should be allowed. + * Also, generated SQL looks like: "... FROM cms_user, cms_article ..." which + * may not work on all dbms. + * + * The main use case for this generalized style of join is when a join condition + * does not involve a foreign key relationship that is mapped to an entity relationship. + */ + public function testImplicitJoinWithCartesianProductAndConditionInWhere() + { + $this->assertValidDql("SELECT u.*, a.* FROM CmsUser u, CmsArticle a WHERE u.name = a.topic"); + } + + public function testImplicitJoinInWhereOnSingleValuedAssociationPathExpression() + { + // This should be allowed because avatar is a single-value association. + // SQL: SELECT ... FROM forum_user fu INNER JOIN forum_avatar fa ON fu.avatar_id = fa.id WHERE fa.id = ? + $this->assertValidDql("SELECT u.* FROM ForumUser u WHERE u.avatar.id = ?"); + } + + public function testImplicitJoinInWhereOnCollectionValuedPathExpression() + { + // This should be forbidden, because articles is a collection + $this->assertInvalidDql("SELECT u.* FROM CmsUser u WHERE u.articles.title = ?"); + } public function testInvalidSyntaxIsRejected() { $this->assertInvalidDql("FOOBAR CmsUser"); - $this->assertInvalidDql("DELETE FROM CmsUser.articles"); - $this->assertInvalidDql("DELETE FROM CmsUser cu WHERE cu.articles.id > ?"); + $this->assertInvalidDql("SELECT user FROM CmsUser user"); + + // Error message here is: Relation 'comments' does not exist in component 'CmsUser' + // This means it is intepreted as: + // SELECT u.* FROM CmsUser u JOIN u.articles JOIN u.comments + // This seems wrong. "JOIN u.articles.comments" should not be allowed. + $this->assertInvalidDql("SELECT u.* FROM CmsUser u JOIN u.articles.comments"); + + // Currently UNDEFINED OFFSET error + $this->assertInvalidDql("SELECT * FROM CmsUser.articles.comments"); } } diff --git a/tests/lib/mocks/Doctrine_DatabasePlatformMock.php b/tests/lib/mocks/Doctrine_DatabasePlatformMock.php index 7f2eff934..df71b005c 100644 --- a/tests/lib/mocks/Doctrine_DatabasePlatformMock.php +++ b/tests/lib/mocks/Doctrine_DatabasePlatformMock.php @@ -2,7 +2,7 @@ class Doctrine_DatabasePlatformMock extends Doctrine_DatabasePlatform { - public function getNativeDeclaration($field) {} + public function getNativeDeclaration(array $field) {} public function getPortableDeclaration(array $field) {} }