diff --git a/lib/Doctrine/DataDict.php b/lib/Doctrine/DataDict.php index b7e1b45db..b967f3667 100644 --- a/lib/Doctrine/DataDict.php +++ b/lib/Doctrine/DataDict.php @@ -31,6 +31,45 @@ * @author Lukas Smith (PEAR MDB2 library) */ class Doctrine_DataDict extends Doctrine_Connection_Module { + /** + * Obtain an array of changes that may need to applied + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + */ + public function compareDefinition($current, $previous) { + $type = !empty($current['type']) ? $current['type'] : null; + if (!method_exists($this, "_compare{$type}Definition")) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'type "'.$current['type'].'" is not yet supported', __FUNCTION__); + } + + if (empty($previous['type']) || $previous['type'] != $type) { + return $current; + } + + $change = $this->{"_compare{$type}Definition"}($current, $previous); + + if ($previous['type'] != $type) { + $change['type'] = true; + } + + $previous_notnull = !empty($previous['notnull']) ? $previous['notnull'] : false; + $notnull = !empty($current['notnull']) ? $current['notnull'] : false; + if ($previous_notnull != $notnull) { + $change['notnull'] = true; + } + + $previous_default = array_key_exists('default', $previous) ? $previous['default'] : + ($previous_notnull ? '' : null); + $default = array_key_exists('default', $current) ? $current['default'] : + ($notnull ? '' : null); + if ($previous_default !== $default) { + $change['default'] = true; + } + + return $change; + } } - diff --git a/lib/Doctrine/DataDict/Informix.php b/lib/Doctrine/DataDict/Informix.php index 584eba668..c32741eb0 100644 --- a/lib/Doctrine/DataDict/Informix.php +++ b/lib/Doctrine/DataDict/Informix.php @@ -50,5 +50,77 @@ class Doctrine_DataDict_Informix extends Doctrine_DataDict { and i.idxname=s.idxname and tr.tabid=r.ptabid and s2.constrid=r.primary and i2.idxname=s2.idxname", ); + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + */ + public function getNativeDeclaration($field) { + switch ($field['type']) { + case 'char': + case 'varchar': + case 'array': + case 'object': + case 'string': + if (empty($field['length']) && array_key_exists('default', $field)) { + $field['length'] = $this->conn->varchar_max_length; + } + + $length = (! empty($field['length'])) ? $field['length'] : false; + $fixed = ((isset($field['fixed']) && $field['fixed']) || $field['type'] == 'char') ? true : false; + + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)') + : ($length ? 'VARCHAR('.$length.')' : 'NVARCHAR'); + case 'clob': + return 'TEXT'; + case 'blob': + return 'BLOB'; + case 'integer': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 1) { + return 'SMALLINT'; + } elseif ($length == 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INTEGER'; + } elseif ($length > 4) { + return 'DECIMAL(20)'; + } + } + return 'INT'; + case 'boolean': + return 'SMALLINT'; + case 'date': + return 'DATE'; + case 'time': + return 'DATETIME YEAR TO SECOND'; + case 'timestamp': + return 'DATETIME'; + case 'float': + return 'FLOAT'; + case 'decimal': + return 'DECIMAL'; + } + return ''; + } } diff --git a/lib/Doctrine/Export.php b/lib/Doctrine/Export.php index c764f0062..13414782e 100644 --- a/lib/Doctrine/Export.php +++ b/lib/Doctrine/Export.php @@ -223,10 +223,44 @@ class Doctrine_Export extends Doctrine_Connection_Module { * 'last_login' => array() * ) * ) - * @throws PDOException * @return void */ public function createIndex($table, $name, array $definition) { + return $this->conn->getDbh()->query($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. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return string + */ + public function createIndexSql($table, $name, array $definition) { $table = $this->conn->quoteIdentifier($table); $name = $this->conn->quoteIdentifier($name); @@ -237,10 +271,8 @@ class Doctrine_Export extends Doctrine_Connection_Module { } $query .= ' ('. implode(', ', $fields) . ')'; - - return $this->conn->getDbh()->query($query); + return $query; } - /** * alter an existing table * (this method is implemented by the drivers) @@ -331,6 +363,98 @@ class Doctrine_Export extends Doctrine_Connection_Module { * @return void */ public function alterTable($name, array $changes, $check) { + $this->conn->getDbh()->query($this->alterTableSql($name, $changes, $check)); + } + /** + * alter an existing table + * (this method is implemented by the drivers) + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @return string + */ + public function alterTableSql($name, array $changes, $check) { throw new Doctrine_Export_Exception('Alter table not supported by this driver.'); } /** diff --git a/lib/Doctrine/Export/Sqlite.php b/lib/Doctrine/Export/Sqlite.php index feecf4905..b67a21a75 100644 --- a/lib/Doctrine/Export/Sqlite.php +++ b/lib/Doctrine/Export/Sqlite.php @@ -74,10 +74,10 @@ class Doctrine_Export_Sqlite extends Doctrine_Export { if(isset($field['sorting'])) { switch ($field['sorting']) { case 'ascending': - $fieldString.= ' ASC'; + $fieldString .= ' ASC'; break; case 'descending': - $fieldString.= ' DESC'; + $fieldString .= ' DESC'; break; } } diff --git a/lib/Doctrine/Hydrate.php b/lib/Doctrine/Hydrate.php index cfc8ee475..b0a69e096 100644 --- a/lib/Doctrine/Hydrate.php +++ b/lib/Doctrine/Hydrate.php @@ -55,9 +55,9 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { */ protected $params = array(); /** - * @var Doctrine_Connection $connection Doctrine_Connection object + * @var Doctrine_Connection $conn Doctrine_Connection object */ - protected $connection; + protected $conn; /** * @var Doctrine_View $view Doctrine_View object */ @@ -115,7 +115,7 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { if( ! ($connection instanceof Doctrine_Connection)) $connection = Doctrine_Manager::getInstance()->getCurrentConnection(); - $this->connection = $connection; + $this->conn = $connection; $this->aliasHandler = new Doctrine_Hydrate_Alias(); } /** @@ -240,7 +240,7 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { * @return Doctrine_Connection */ public function getConnection() { - return $this->connection; + return $this->conn; } /** * setView @@ -340,10 +340,10 @@ abstract class Doctrine_Hydrate extends Doctrine_Access { $query = $this->view->getSelectSql(); if($this->isLimitSubqueryUsed() && - $this->connection->getDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') + $this->conn->getDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') $params = array_merge($params, $params); - $stmt = $this->connection->execute($query, $params); + $stmt = $this->conn->execute($query, $params); if($this->aggregate) return $stmt->fetchAll(PDO::FETCH_ASSOC); diff --git a/lib/Doctrine/Query.php b/lib/Doctrine/Query.php index a923f4def..2f86aaf11 100644 --- a/lib/Doctrine/Query.php +++ b/lib/Doctrine/Query.php @@ -18,6 +18,7 @@ * and is licensed under the LGPL. For more information, see * . */ +Doctrine::autoload('Doctrine_Hydrate'); /** * Doctrine_Query * @@ -457,7 +458,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { public function getQueryBase() { switch($this->type) { case self::DELETE: - if($this->connection->getName() == 'mysql') + if($this->conn->getName() == 'mysql') $q = 'DELETE '.end($this->tableAliases).' FROM '; else $q = 'DELETE FROM '; @@ -501,15 +502,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { if($this->isDistinct()) $str = 'DISTINCT '; - $q = 'SELECT '.$str.implode(", ",$this->parts["select"]). - ' FROM '; $q = $this->getQueryBase(); $q .= $this->parts['from']; if( ! empty($this->parts['join'])) { foreach($this->parts['join'] as $part) { - $q .= " ".implode(' ', $part); + $q .= ' '.implode(' ', $part); } } @@ -527,10 +526,10 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { $subquery = $this->getLimitSubquery(); - switch(strtolower($this->connection->getName())) { + switch(strtolower($this->conn->getName())) { case 'mysql': // mysql doesn't support LIMIT in subqueries - $list = $this->connection->execute($subquery, $params)->fetchAll(PDO::FETCH_COLUMN); + $list = $this->conn->execute($subquery, $params)->fetchAll(PDO::FETCH_COLUMN); $subquery = implode(', ', $list); break; case 'pgsql': @@ -555,7 +554,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { $q .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(' ', $this->parts['orderby']):''; if($modifyLimit) - $q = $this->connection->modifyLimitQuery($q, $this->parts['limit'], $this->parts['offset']); + $q = $this->conn->modifyLimitQuery($q, $this->parts['limit'], $this->parts['offset']); // return to the previous state if( ! empty($string)) @@ -586,7 +585,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { // initialize the base of the subquery $subquery = 'SELECT DISTINCT ' . $primaryKey; - if($this->connection->getDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') { + if($this->conn->getDBH()->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') { // pgsql needs the order by fields to be preserved in select clause foreach($this->parts['orderby'] as $part) { @@ -623,7 +622,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { $subquery .= ( ! empty($this->parts['orderby']))? ' ORDER BY ' . implode(' ', $this->parts['orderby']):''; // add driver specific limit clause - $subquery = $this->connection->modifyLimitQuery($subquery, $this->parts['limit'], $this->parts['offset']); + $subquery = $this->conn->modifyLimitQuery($subquery, $this->parts['limit'], $this->parts['offset']); $parts = self::quoteExplode($subquery, ' ', "'", "'"); @@ -1059,7 +1058,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { if($key == 0) { $currPath = substr($currPath,1); - $table = $this->connection->getTable($name); + $table = $this->conn->getTable($name); $tname = $this->aliasHandler->getShortAlias($table->getTableName()); @@ -1068,7 +1067,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { $this->tableIndexes[$tname] = 1; - $this->parts["from"] = $table->getTableName() . ' ' . $tname; + $this->parts["from"] = $this->conn->quoteIdentifier($table->getTableName()) . ' ' . $tname; $this->tableAliases[$currPath] = $tname; @@ -1097,7 +1096,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { } else $tname2 = $this->aliasHandler->generateShortAlias($original); - $aliasString = $original . ' ' . $tname2; + $aliasString = $this->conn->quoteIdentifier($original) . ' ' . $tname2; switch($mark) { case ':': @@ -1113,7 +1112,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { if( ! $fk->isOneToOne()) { $this->needsSubquery = true; } - + if( ! $loadFields || $fk->getTable()->usesInheritanceMap()) { $this->subqueryAliases[] = $tname2; } @@ -1126,16 +1125,17 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { if( ! $loadFields) { $this->subqueryAliases[] = $assocTableName; } - $this->parts["join"][$tname][$assocTableName] = $join.$assocTableName . ' ON ' .$tname . '.' + $this->parts["join"][$tname][$assocTableName] = $join . $assocTableName . ' ON ' . $tname . '.' . $table->getIdentifier() . ' = ' . $assocTableName . '.' . $fk->getLocal(); - $this->parts["join"][$tname][$tname2] = $join.$aliasString . ' ON ' .$tname2 . '.' + $this->parts["join"][$tname][$tname2] = $join . $aliasString . ' ON ' . $tname2 . '.' . $table->getIdentifier() . ' = ' . $assocTableName . '.' . $fk->getForeign(); } else { - $this->parts["join"][$tname][$tname2] = $join.$aliasString . ' ON ' .$tname . '.' + $this->parts["join"][$tname][$tname2] = $join . $aliasString + . ' ON ' . $tname . '.' . $fk->getLocal() . ' = ' . $tname2 . '.' . $fk->getForeign(); } @@ -1160,8 +1160,12 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { $this->tables[$tableName] = $table; if($loadFields) { - + $skip = false; + + if( ! empty($this->pendingFields)) + $skip = true; + if($componentAlias) { $this->compAliases[$componentAlias] = $currPath; @@ -1174,6 +1178,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { $skip = true; } } + if( ! $skip) $this->parseFields($fullname, $tableName, $e2, $currPath); } diff --git a/lib/Doctrine/RawSql.php b/lib/Doctrine/RawSql.php index 517b68391..a4e05ca05 100644 --- a/lib/Doctrine/RawSql.php +++ b/lib/Doctrine/RawSql.php @@ -22,14 +22,14 @@ Doctrine::autoload('Doctrine_Hydrate'); /** * Doctrine_RawSql * - * @package Doctrine - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @category Object Relational Mapping - * @link www.phpdoctrine.com - * @since 1.0 - * @version $Revision$ - * @author Konsta Vesterinen - */ + * @package Doctrine + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @category Object Relational Mapping + * @link www.phpdoctrine.com + * @since 1.0 + * @version $Revision$ + * @author Konsta Vesterinen + */ class Doctrine_RawSql extends Doctrine_Hydrate { /** * @var array $fields @@ -217,9 +217,9 @@ class Doctrine_RawSql extends Doctrine_Hydrate { $tableName = $table->getAliasName($component); - $table = $this->connection->getTable($tableName); + $table = $this->conn->getTable($tableName); } else { - $table = $this->connection->getTable($component); + $table = $this->conn->getTable($component); } $this->tables[$alias] = $table; $this->fetchModes[$alias] = Doctrine::FETCH_IMMEDIATE; diff --git a/lib/Doctrine/Record.php b/lib/Doctrine/Record.php index 158ecc1af..a34ed3548 100644 --- a/lib/Doctrine/Record.php +++ b/lib/Doctrine/Record.php @@ -1364,6 +1364,9 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite $this->_table->getOption($name); else $this->_table->setOption($name, $value); + } + public function hasIndex($name ) { + } /** * addListener diff --git a/lib/Doctrine/Schema/Object.php b/lib/Doctrine/Schema/Object.php index 5860ea53f..72155407a 100644 --- a/lib/Doctrine/Schema/Object.php +++ b/lib/Doctrine/Schema/Object.php @@ -19,17 +19,10 @@ * . */ Doctrine::autoload('Doctrine_Access'); -/** - * @package Doctrine - * @url http://www.phpdoctrine.com - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @author Jukka Hassinen - * @author Konsta Vesterinen - * @version $Id$ - */ /** * class Doctrine_Schema_Object * Catches any non-property call from child classes and throws an exception. + * * @package Doctrine * @category Object Relational Mapping * @link www.phpdoctrine.com @@ -37,6 +30,7 @@ Doctrine::autoload('Doctrine_Access'); * @since 1.0 * @version $Revision$ * @author Konsta Vesterinen + * @author Jukka Hassinen */ abstract class Doctrine_Schema_Object extends Doctrine_Access implements IteratorAggregate, Countable { diff --git a/lib/Doctrine/Table.php b/lib/Doctrine/Table.php index eecbda668..91f54aeb7 100644 --- a/lib/Doctrine/Table.php +++ b/lib/Doctrine/Table.php @@ -775,7 +775,7 @@ class Doctrine_Table extends Doctrine_Configurable implements Countable { * @return string */ final public function getTableName() { - return $this->conn->quoteIdentifier($this->options['tableName']); + return $this->options['tableName']; } /** * create diff --git a/manual/codes/Basic Components - Query - FROM - selecting tables.php b/manual/codes/Basic Components - Query - FROM - selecting tables.php index 430d8005c..677533c40 100644 --- a/manual/codes/Basic Components - Query - FROM - selecting tables.php +++ b/manual/codes/Basic Components - Query - FROM - selecting tables.php @@ -1,27 +1,24 @@ query("FROM User"); - -// find all users with only their names (and primary keys) fetched - -$coll = $conn->query("FROM User(name)"); - // find all groups -$coll = $conn->query("FROM Group"); +$coll = $q->from("FROM Group"); // find all users and user emails -$coll = $conn->query("FROM User.Email"); +$coll = $q->from("FROM User.Email"); -// find all users and user emails with only user name and +// find all users and user emails with only user name and // age + email address loaded -$coll = $conn->query("FROM User(name, age).Email(address)"); +$coll = $q->select('u.name, u.age, e.address') + ->from('FROM User u') + ->leftJoin('u.Email e') + ->execute(); // find all users, user email and user phonenumbers -$coll = $conn->query("FROM User.Email, User.Phonenumber"); +$coll = $q->from('FROM User u') + ->leftJoin('u.Email e') + ->leftJoin('u.Phonenumber p') + ->execute(); ?> diff --git a/manual/docs/Basic Components - Query - FROM - selecting tables.php b/manual/docs/Basic Components - Query - FROM - selecting tables.php index de4d393c4..3cc55e884 100644 --- a/manual/docs/Basic Components - Query - FROM - selecting tables.php +++ b/manual/docs/Basic Components - Query - FROM - selecting tables.php @@ -1,3 +1,18 @@ -DQL FROM -part is used for selecting tables as well as for selecting fields. Related components are selected either -with colon-operator or dot-operator (See Relation operators).
You can place -the selected fields in () -brackets (eg. 'FROM User(name, id)'). If you are about to select all fields you can simple use 'FROM User'. + +The FROM clause indicates the component or components from which to retrieve records. +If you name more than one component, you are performing a join. +For each table specified, you can optionally specify an alias. Doctrine_Query offers easy to use +methods such as from(), addFrom(), leftJoin() and innerJoin() for managing the FROM part of your DQL query. + +from('User')->execute(); + +// find all users with only their names (and primary keys) fetched + +\$coll = \$q->select('u.name')->('User u'); +?>"); +?> diff --git a/manual/features.php b/manual/features.php index f1870353e..992651d41 100644 --- a/manual/features.php +++ b/manual/features.php @@ -1,69 +1,97 @@ -
-FEATURES:
-
-GENERAL FEATURES
-    - Fully object-oriented following best practices and design patterns
-    - Multiple databases
-    - Database connection pooling with connection-record -registry
-    - Runtime configuration (no XML needed!)
-    - Very modular structure (only uses the needed features)
-    - The whole framework can be compiled into a single file
-    - Leveled configuration (attributes can be set at global, connection and table levels)
-
-DATABASE ABSTRACTION:
-    - A DSN (data source name) or array format for specifying database servers
-    - Datatype abstraction and on demand datatype conversion
-    - supports PDO
-    - Database query profiling
-    - Query caching
-    - Sequence / autoincrement emulation
-    - Replace emulation
-    - RDBMS management methods (creating, dropping, altering)
-    - SQL function call abstraction
-    - SQL expression abstraction
-    - Pattern matching abstraction
-    - Portable error codes
-    - Nested transactions
-    - Transaction isolation abstraction
-    - Transaction savepoint abstraction
-    - Index/Unique Key/Primary Key support
-    - Ability to read the information schema
-    - Reverse engineering schemas from an existing database
-    - LIMIT / OFFSET emulation
+
 
 
-OBJECT RELATIONAL MAPPING:
-    General features:
-        - Validators
-        - Transactional errorStack for easy retrieval of all errors
-        - EventListeners
-        - UnitOfWork pattern (easy saving of all pending objects)
-        - Uses ActiveRecord pattern
-        - State-wise records and transactions
-        - Importing existing database schemas to Doctrine ActiveRecord objects
-        - Exporting Doctrine ActiveRecords to database (= automatic table creation)
+
+    
+        
+ + + + + + + + + + +
+ Doctrine - PHP Data Persistence and ORM Tool +
+
- Mapping: - - Composite, Natural, Autoincremented and Sequential identifiers - - PHP Array / Object data types for columns (automatic serialization/unserialization) - - Gzip datatype for all databases - - Emulated enum datatype for all databases - - Datatype abstraction - - Column aggregation inheritance - - One-class-one-table inheritance as well as One-table - - One-to-many, many-to-one, one-to-one and many-to-many relations - - Self-referencing relations even for association table relations - - Relation aliases - Object population: - - DQL (Doctrine Query Language), an EJB 3 spec compliant OQL - - The limit-subquery-algorithm - - OO-style query API for both DQL and raw SQL - - Object population from database views - - Object population through raw SQL - - Transactions and locking: - - Pessimistic offline locking - - Savepoints, transaction isolation levels and nested transactions - - Transactional query optimization (gathering of DELETE statements) - +
    +GENERAL FEATURES +
      +
    • Fully object-oriented following best practices and design patterns +
    • Multiple databases +
    • Database connection pooling with connection-record -registry +
    • Runtime configuration (no XML needed!) +
    • Very modular structure (only uses the needed features) +
    • The whole framework can be compiled into a single file +
    • Leveled configuration (attributes can be set at global, connection and table levels) +
    +
    +DATABASE ABSTRACTION +
      +
    • A DSN (data source name) or array format for specifying database servers +
    • Datatype abstraction and on demand datatype conversion +
    • supports PDO +
    • Database query profiling +
    • Query caching +
    • Sequence / autoincrement emulation +
    • Replace emulation +
    • RDBMS management methods (creating, dropping, altering) +
    • SQL function call abstraction +
    • SQL expression abstraction +
    • Pattern matching abstraction +
    • Portable error codes +
    • Nested transactions +
    • Transaction isolation abstraction +
    • Transaction savepoint abstraction +
    • Index/Unique Key/Primary Key support +
    • Ability to read the information schema +
    • Reverse engineering schemas from an existing database +
    • LIMIT / OFFSET emulation +
    +
    +OBJECT RELATIONAL MAPPING: +
      + General features +
    • Validators +
    • Transactional errorStack for easy retrieval of all errors +
    • EventListeners +
    • UnitOfWork pattern (easy saving of all pending objects) +
    • Uses ActiveRecord pattern +
    • State-wise records and transactions +
    • Importing existing database schemas to Doctrine ActiveRecord objects +
    • Exporting Doctrine ActiveRecords to database (= automatic table creation) +

      + Mapping +
    • Composite, Natural, Autoincremented and Sequential identifiers +
    • PHP Array / Object data types for columns (automatic serialization/unserialization) +
    • Gzip datatype for all databases +
    • Emulated enum datatype for all databases +
    • Datatype abstraction +
    • Column aggregation inheritance +
    • One-class-one-table inheritance as well as One-table +
    • One-to-many, many-to-one, one-to-one and many-to-many relations +
    • Self-referencing relations even for association table relations +
    • Relation aliases +

      + Object population +
    • DQL (Doctrine Query Language), an EJB 3 spec compliant OQL +
    • The limit-subquery-algorithm +
    • OO-style query API for both DQL and raw SQL +
    • Object population from database views +
    • Object population through raw SQL +

      + Transactions and locking +
    • Pessimistic offline locking +
    • Savepoints, transaction isolation levels and nested transactions +
    • Transactional query optimization (gathering of DELETE statements) +
    +
+
diff --git a/tests/QueryLimitTestCase.php b/tests/QueryLimitTestCase.php index 044ce5d27..654ebc5ae 100644 --- a/tests/QueryLimitTestCase.php +++ b/tests/QueryLimitTestCase.php @@ -10,7 +10,7 @@ class Doctrine_Query_Limit_TestCase extends Doctrine_UnitTestCase { public function testLimitWithOneToOneLeftJoin() { $q = new Doctrine_Query($this->connection); - $q->from('User(id).Email')->limit(5); + $q->select('u.id, e.*')->from('User u, u.Email e')->limit(5); $users = $q->execute(); $this->assertEqual($users->count(), 5); @@ -19,15 +19,14 @@ class Doctrine_Query_Limit_TestCase extends Doctrine_UnitTestCase { } public function testLimitWithOneToOneInnerJoin() { $q = new Doctrine_Query($this->connection); - $q->from('User(id):Email')->limit(5); + $q->select('u.id, e.*')->from('User u, u:Email e')->limit(5); $users = $q->execute(); $this->assertEqual($users->count(), 5); $this->assertEqual($q->getQuery(), "SELECT e.id AS e__id, e2.id AS e2__id, e2.address AS e2__address FROM entity e INNER JOIN email e2 ON e.email_id = e2.id WHERE (e.type = 0) LIMIT 5"); } public function testLimitWithOneToManyLeftJoin() { - $this->query->from("User(id).Phonenumber"); - $this->query->limit(5); + $this->query->select('u.id, p.*')->from('User u, u.Phonenumber p')->limit(5); $sql = $this->query->getQuery(); $this->assertEqual($this->query->getQuery(), @@ -87,7 +86,7 @@ class Doctrine_Query_Limit_TestCase extends Doctrine_UnitTestCase { } public function testLimitWithOneToManyInnerJoin() { - $this->query->select('u.id')->from("User u INNER JOIN u.Phonenumber"); + $this->query->select('u.id, p.*')->from('User u INNER JOIN u.Phonenumber p'); $this->query->limit(5); @@ -114,8 +113,8 @@ class Doctrine_Query_Limit_TestCase extends Doctrine_UnitTestCase { public function testLimitWithPreparedQueries() { $q = new Doctrine_Query(); - $q->from("User(id).Phonenumber(id)"); - $q->where("User.name = ?"); + $q->select('u.id, p.id')->from('User u LEFT JOIN u.Phonenumber p'); + $q->where("u.name = ?"); $q->limit(5); $users = $q->execute(array('zYne')); @@ -128,7 +127,7 @@ class Doctrine_Query_Limit_TestCase extends Doctrine_UnitTestCase { 'SELECT e.id AS e__id, p.id AS p__id FROM entity e LEFT JOIN phonenumber p ON e.id = p.entity_id WHERE e.id IN (SELECT DISTINCT e2.id FROM entity e2 WHERE e2.name = ? AND (e2.type = 0) LIMIT 5) AND e.name = ? AND (e.type = 0)'); $q = new Doctrine_Query(); - $q->from("User(id).Phonenumber(id)"); + $q->select('u.id, p.id')->from('User u LEFT JOIN u.Phonenumber p'); $q->where("User.name LIKE ? || User.name LIKE ?"); $q->limit(5); $users = $q->execute(array('%zYne%', '%Arnold%')); diff --git a/tests/QuerySelectTestCase.php b/tests/QuerySelectTestCase.php index ee036e8b1..153d262a2 100644 --- a/tests/QuerySelectTestCase.php +++ b/tests/QuerySelectTestCase.php @@ -17,7 +17,7 @@ class Doctrine_Query_Select_TestCase extends Doctrine_UnitTestCase { $this->assertEqual($q->getQuery(), 'SELECT COUNT(e.id) AS e__0 FROM entity e WHERE (e.type = 0)'); } - public function testMultipleAggregateFunctions() { + public function testSelectPartSupportsMultipleAggregateFunctions() { $q = new Doctrine_Query(); $q->parseQuery('SELECT MAX(u.id), MIN(u.name) FROM User u'); diff --git a/tests/run.php b/tests/run.php index 3358c8cc9..b56f22008 100644 --- a/tests/run.php +++ b/tests/run.php @@ -43,6 +43,7 @@ require_once('QuerySelectTestCase.php'); require_once('QueryShortAliasesTestCase.php'); require_once('QueryDeleteTestCase.php'); require_once('QueryUpdateTestCase.php'); +require_once('QueryIdentifierQuotingTestCase.php'); require_once('UnitOfWorkTestCase.php'); @@ -146,8 +147,8 @@ $test->addTestCase(new Doctrine_Export_Firebird_TestCase()); $test->addTestCase(new Doctrine_Configurable_TestCase()); + */ - */ $test->addTestCase(new Doctrine_Transaction_TestCase()); @@ -258,6 +259,7 @@ $test->addTestCase(new Doctrine_Query_Limit_TestCase()); $test->addTestCase(new Doctrine_Query_Select_TestCase()); +$test->addTestCase(new Doctrine_Query_IdentifierQuoting_TestCase()); //$test->addTestCase(new Doctrine_Cache_Query_SqliteTestCase());