summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes5
-rw-r--r--framework/Data/ActiveRecord/TActiveRecord.php156
-rw-r--r--framework/Data/ActiveRecord/TActiveRecordGateway.php5
-rw-r--r--framework/Data/ActiveRecord/TActiveRecordRelation.php124
-rw-r--r--framework/Data/Common/Mssql/TMssqlMetaData.php2
-rw-r--r--framework/Data/Common/Mysql/TMysqlMetaData.php71
-rw-r--r--framework/Data/Common/Pgsql/TPgsqlMetaData.php2
-rw-r--r--framework/Data/Common/Sqlite/TSqliteMetaData.php2
-rw-r--r--framework/Data/DataGateway/TDataGatewayCommand.php26
-rw-r--r--tests/simple_unit/ActiveRecord/ActiveRecordMySql5TestCase.php5
-rw-r--r--tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php114
-rw-r--r--tests/simple_unit/ActiveRecord/RecordEventTestCase.php10
-rw-r--r--tests/simple_unit/ActiveRecord/blog.dbbin0 -> 4096 bytes
-rw-r--r--tests/simple_unit/ActiveRecord/mysql4text.sql52
-rw-r--r--tests/simple_unit/DbCommon/Mysql4ColumnTest.php254
-rw-r--r--tests/simple_unit/DbCommon/MysqlColumnTest.php5
16 files changed, 751 insertions, 82 deletions
diff --git a/.gitattributes b/.gitattributes
index e27ec026..e1cbbc69 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1626,6 +1626,7 @@ framework/Data/ActiveRecord/TActiveRecordConfig.php -text
framework/Data/ActiveRecord/TActiveRecordCriteria.php -text
framework/Data/ActiveRecord/TActiveRecordGateway.php -text
framework/Data/ActiveRecord/TActiveRecordManager.php -text
+framework/Data/ActiveRecord/TActiveRecordRelation.php -text
framework/Data/ActiveRecord/TActiveRecordStateRegistry.php -text
framework/Data/Common/IbmDb2/TIbmColumnMetaData.php -text
framework/Data/Common/IbmDb2/TIbmMetaData.php -text
@@ -2630,11 +2631,14 @@ tests/simple_unit/ActiveRecord/CriteriaTestCase.php -text
tests/simple_unit/ActiveRecord/DeleteByPkTestCase.php -text
tests/simple_unit/ActiveRecord/FindByPksTestCase.php -text
tests/simple_unit/ActiveRecord/FindBySqlTestCase.php -text
+tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php -text
tests/simple_unit/ActiveRecord/RecordEventTestCase.php -text
tests/simple_unit/ActiveRecord/SqliteTestCase.php -text
tests/simple_unit/ActiveRecord/UserRecordTestCase.php -text
tests/simple_unit/ActiveRecord/ViewRecordTestCase.php -text
tests/simple_unit/ActiveRecord/ar_test.db -text
+tests/simple_unit/ActiveRecord/blog.db -text
+tests/simple_unit/ActiveRecord/mysql4text.sql -text
tests/simple_unit/ActiveRecord/records/Blogs.php -text
tests/simple_unit/ActiveRecord/records/DepSections.php -text
tests/simple_unit/ActiveRecord/records/DepartmentRecord.php -text
@@ -2645,6 +2649,7 @@ tests/simple_unit/DbCommon/CommandBuilderMssqlTest.php -text
tests/simple_unit/DbCommon/CommandBuilderMysqlTest.php -text
tests/simple_unit/DbCommon/CommandBuilderPgsqlTest.php -text
tests/simple_unit/DbCommon/MssqlColumnTest.php -text
+tests/simple_unit/DbCommon/Mysql4ColumnTest.php -text
tests/simple_unit/DbCommon/MysqlColumnTest.php -text
tests/simple_unit/DbCommon/PgsqlColumnTest.php -text
tests/simple_unit/DbCommon/SqliteColumnTest.php -text
diff --git a/framework/Data/ActiveRecord/TActiveRecord.php b/framework/Data/ActiveRecord/TActiveRecord.php
index 1ea06a60..509d23f6 100644
--- a/framework/Data/ActiveRecord/TActiveRecord.php
+++ b/framework/Data/ActiveRecord/TActiveRecord.php
@@ -12,14 +12,15 @@
Prado::using('System.Data.ActiveRecord.TActiveRecordManager');
Prado::using('System.Data.ActiveRecord.TActiveRecordCriteria');
+Prado::using('System.Data.ActiveRecord.TActiveRecordRelation');
/**
* Base class for active records.
*
* An active record creates an object that wraps a row in a database table
- * or view, encapsulates the database access, and adds domain logic on that data.
- *
- * Active record objects are stateful, this is main difference between the
+ * or view, encapsulates the database access, and adds domain logic on that data.
+ *
+ * Active record objects are stateful, this is main difference between the
* TActiveRecord implementation and the TTableGateway implementation.
*
* The essence of an Active Record is an object model of the
@@ -68,6 +69,10 @@ Prado::using('System.Data.ActiveRecord.TActiveRecordCriteria');
*/
abstract class TActiveRecord extends TComponent
{
+ const HAS_MANY='HAS_MANY';
+ const BELONGS_TO='BELONGS_TO';
+ const HAS_ONE='HAS_ONE';
+
/**
* @var boolean true if this class is read only.
*/
@@ -143,10 +148,10 @@ abstract class TActiveRecord extends TComponent
}
/**
- * Returns the instance of a active record finder for a particular class.
- * The finder objects are static instances for each ActiveRecord class.
- * This means that event handlers bound to these finder instances are class wide.
- * Create a new instance of the ActiveRecord class if you wish to bound the
+ * Returns the instance of a active record finder for a particular class.
+ * The finder objects are static instances for each ActiveRecord class.
+ * This means that event handlers bound to these finder instances are class wide.
+ * Create a new instance of the ActiveRecord class if you wish to bound the
* event handlers to object instance.
* @param string active record class name.
* @return TActiveRecord active record finder instance.
@@ -175,14 +180,14 @@ abstract class TActiveRecord extends TComponent
public function getRecordManager()
{
return TActiveRecordManager::getInstance();
- }
-
- /**
- * @return TActiveRecordGateway record table gateway.
- */
- public function getRecordGateway()
- {
- return $this->getRecordManager()->getRecordGateway();
+ }
+
+ /**
+ * @return TActiveRecordGateway record table gateway.
+ */
+ public function getRecordGateway()
+ {
+ return $this->getRecordManager()->getRecordGateway();
}
/**
@@ -281,16 +286,16 @@ abstract class TActiveRecord extends TComponent
//try the cache (the cache object must be clean)
if(!is_null($obj = $registry->getCachedInstance($data)))
- return $obj;
-
- $gateway = $this->getRecordManager()->getRecordGateway();
+ return $obj;
+
+ $gateway = $this->getRecordManager()->getRecordGateway();
//create and populate the object
$obj = Prado::createComponent($type);
- $tableInfo = $gateway->getRecordTableInfo($obj);
- foreach($tableInfo->getColumns()->getKeys() as $name)
- {
- if(isset($data[$name]))
+ $tableInfo = $gateway->getRecordTableInfo($obj);
+ foreach($tableInfo->getColumns()->getKeys() as $name)
+ {
+ if(isset($data[$name]))
$obj->{$name} = $data[$name];
}
@@ -420,6 +425,17 @@ abstract class TActiveRecord extends TComponent
}
/**
+ *
+ *
+ */
+ public function findAllByIndex($criteria,$fields,$values)
+ {
+ $gateway = $this->getRecordManager()->getRecordGateway();
+ $result = $gateway->findRecordsByIndex($this,$criteria,$fields,$values);
+ return $this->collectObjects($result);
+ }
+
+ /**
* Find the number of records.
* @param string|TActiveRecordCriteria SQL condition or criteria object.
* @param mixed parameter values.
@@ -435,6 +451,24 @@ abstract class TActiveRecord extends TComponent
}
/**
+ *
+ * @return TActiveRecordRelation
+ */
+ protected function getRecordRelation($property,$args)
+ {
+ $criteria = $this->getCriteria(count($args)>0 ? $args[0] : null, array_slice($args,1));
+ $relation = $this->{$property};
+ switch($relation[0])
+ {
+ case self::HAS_MANY:
+ $finder = self::finder($relation[1]);
+ return new TActiveRecordHasMany($this, $criteria, $finder, $property);
+ default:
+ throw new TException('Not done yet');
+ }
+ }
+
+ /**
* Dynamic find method using parts of method name as search criteria.
* Method name starting with "findBy" only returns 1 record.
* Method name starting with "findAllBy" returns 0 or more records.
@@ -466,7 +500,12 @@ abstract class TActiveRecord extends TComponent
public function __call($method,$args)
{
$delete =false;
- if($findOne = substr(strtolower($method),0,6)==='findby')
+ if(substr(strtolower($method),0,4)==='with')
+ {
+ $property= $method[4]==='_' ? substr($method,5) : substr($method,4);
+ return $this->getRecordRelation($property, $args);
+ }
+ else if($findOne = substr(strtolower($method),0,6)==='findby')
$condition = $method[6]==='_' ? substr($method,7) : substr($method,6);
else if(substr(strtolower($method),0,9)==='findallby')
$condition = $method[9]==='_' ? substr($method,10) : substr($method,9);
@@ -492,7 +531,7 @@ abstract class TActiveRecord extends TComponent
* @param array additional parameters obtained from function_get_args().
* @return TSqlCriteria criteria object.
*/
- protected function getCriteria($criteria, $parameters, $args)
+ protected function getCriteria($criteria, $parameters, $args=array())
{
if(is_string($criteria))
{
@@ -502,40 +541,41 @@ abstract class TActiveRecord extends TComponent
else if($criteria instanceof TSqlCriteria)
return $criteria;
else
- throw new TActiveRecordException('ar_invalid_criteria');
+ return new TActiveRecordCriteria();
+ //throw new TActiveRecordException('ar_invalid_criteria');
+ }
+
+ /**
+ * Raised when a command is prepared and parameter binding is completed.
+ * The parameter object is TDataGatewayEventParameter of which the
+ * {@link TDataGatewayEventParameter::getCommand Command} property can be
+ * inspected to obtain the sql query to be executed.
+ *
+ * Note well that the finder objects obtained from ActiveRecord::finder()
+ * method are static objects. This means that the event handlers are
+ * bound to a static finder object and not to each distinct active record object.
+ * @param TDataGatewayEventParameter
+ */
+ public function onCreateCommand($param)
+ {
+ $this->raiseEvent('OnCreateCommand', $this, $param);
+ }
+
+ /**
+ * Raised when a command is executed and the result from the database was returned.
+ * The parameter object is TDataGatewayResultEventParameter of which the
+ * {@link TDataGatewayEventParameter::getResult Result} property contains
+ * the data return from the database. The data returned can be changed
+ * by setting the {@link TDataGatewayEventParameter::setResult Result} property.
+ *
+ * Note well that the finder objects obtained from ActiveRecord::finder()
+ * method are static objects. This means that the event handlers are
+ * bound to a static finder object and not to each distinct active record object.
+ * @param TDataGatewayResultEventParameter
+ */
+ public function onExecuteCommand($param)
+ {
+ $this->raiseEvent('OnExecuteCommand', $this, $param);
}
-
- /**
- * Raised when a command is prepared and parameter binding is completed.
- * The parameter object is TDataGatewayEventParameter of which the
- * {@link TDataGatewayEventParameter::getCommand Command} property can be
- * inspected to obtain the sql query to be executed.
- *
- * Note well that the finder objects obtained from ActiveRecord::finder()
- * method are static objects. This means that the event handlers are
- * bound to a static finder object and not to each distinct active record object.
- * @param TDataGatewayEventParameter
- */
- public function onCreateCommand($param)
- {
- $this->raiseEvent('OnCreateCommand', $this, $param);
- }
-
- /**
- * Raised when a command is executed and the result from the database was returned.
- * The parameter object is TDataGatewayResultEventParameter of which the
- * {@link TDataGatewayEventParameter::getResult Result} property contains
- * the data return from the database. The data returned can be changed
- * by setting the {@link TDataGatewayEventParameter::setResult Result} property.
- *
- * Note well that the finder objects obtained from ActiveRecord::finder()
- * method are static objects. This means that the event handlers are
- * bound to a static finder object and not to each distinct active record object.
- * @param TDataGatewayResultEventParameter
- */
- public function onExecuteCommand($param)
- {
- $this->raiseEvent('OnExecuteCommand', $this, $param);
- }
}
?>
diff --git a/framework/Data/ActiveRecord/TActiveRecordGateway.php b/framework/Data/ActiveRecord/TActiveRecordGateway.php
index c3239c5c..40948999 100644
--- a/framework/Data/ActiveRecord/TActiveRecordGateway.php
+++ b/framework/Data/ActiveRecord/TActiveRecordGateway.php
@@ -221,6 +221,11 @@ class TActiveRecordGateway extends TComponent
return $this->getCommand($record)->findBySql($criteria);
}
+ public function findRecordsByIndex(TActiveRecord $record, $criteria, $fields, $values)
+ {
+ return $this->getCommand($record)->findAllByIndex($criteria,$fields,$values);
+ }
+
/**
* Returns the number of records that match the given criteria.
* @param TActiveRecord active record finder instance.
diff --git a/framework/Data/ActiveRecord/TActiveRecordRelation.php b/framework/Data/ActiveRecord/TActiveRecordRelation.php
new file mode 100644
index 00000000..6fae5499
--- /dev/null
+++ b/framework/Data/ActiveRecord/TActiveRecordRelation.php
@@ -0,0 +1,124 @@
+<?php
+
+abstract class TActiveRecordRelation
+{
+}
+
+class TActiveRecordHasMany extends TActiveRecordRelation
+{
+ private $source;
+ private $dependent;
+ private $property;
+ private $criteria;
+
+ private $fkeys;
+
+ public function __construct($source,$criteria,$dependent,$property)
+ {
+ $this->source=$source;
+ $this->criteria=$criteria;
+ $this->dependent=$dependent;
+ $this->property=$property;
+ }
+
+ public function __call($method,$args)
+ {
+ $results = call_user_func_array(array($this->source,$method),$args);
+ $fkResults = $this->getForeignIndexResults($results);
+ $this->matchResultCollection($results,$fkResults);
+ return $results;
+ }
+ protected function getForeignIndexResults($results)
+ {
+ if(!is_array($results))
+ $results = array($results);
+ $fkeys = $this->getForeignKeys();
+ $values = $this->getForeignKeyIndices($results, $fkeys);
+ $fields = array_keys($fkeys);
+ return $this->dependent->findAllByIndex($this->criteria, $fields, $values);
+ }
+
+ protected function matchResultCollection(&$results,&$fkResults)
+ {
+ $keys = $this->getForeignKeys();
+ $collections=array();
+ foreach($fkResults as $fkObject)
+ {
+ $objId=array();
+ foreach($keys as $fkName=>$name)
+ $objId[] = $fkObject->{$fkName};
+ $collections[$this->getObjectId($objId)][]=$fkObject;
+ }
+ if(is_array($results))
+ {
+ for($i=0,$k=count($results);$i<$k;$i++)
+ {
+ $this->setFkObjectProperty($results[$i], $collections);
+ }
+ }
+ else
+ {
+ $this->setFkObjectProperty($results, $collections);
+ }
+ }
+
+ function setFKObjectProperty($source, &$collections)
+ {
+ $objId=array();
+ foreach($this->getForeignKeys() as $fkName=>$name)
+ $objId[] = $source->{$name};
+ $key = $this->getObjectId($objId);
+ $source->{$this->property} = isset($collections[$key]) ? $collections[$key] : array();
+ }
+
+ protected function getObjectId($objId)
+ {
+ return sprintf('%x',crc32(serialize($objId)));
+ }
+
+ protected function getForeignKeys()
+ {
+ if($this->fkeys===null)
+ {
+ $gateway = $this->dependent->getRecordGateway();
+ $depTableInfo = $gateway->getRecordTableInfo($this->dependent);
+ $fks = $depTableInfo->getForeignKeys();
+ $sourceTable = $gateway->getRecordTableInfo($this->source)->getTableName();
+ foreach($fks as $relation)
+ {
+ if($relation['table']===$sourceTable)
+ {
+ $this->fkeys=$relation['keys'];
+ break;
+ }
+ }
+ if(!$this->fkeys)
+ throw new TActiveRecordException('no fk defined for '.$depTableInfo->getTableFullName());
+ }
+ return $this->fkeys;
+ }
+
+ protected function getForeignKeyIndices($results,$keys)
+ {
+ $values = array();
+ foreach($results as $result)
+ {
+ $value = array();
+ foreach($keys as $name)
+ $value[] = $result->{$name};
+ $values[] = $value;
+ }
+ return $values;
+ }
+}
+
+class TActiveRecordHasOne extends TActiveRecordRelation
+{
+}
+
+class TActiveRecordBelongsTo extends TActiveRecordRelation
+{
+
+}
+
+?> \ No newline at end of file
diff --git a/framework/Data/Common/Mssql/TMssqlMetaData.php b/framework/Data/Common/Mssql/TMssqlMetaData.php
index 5d14aac1..72d297f7 100644
--- a/framework/Data/Common/Mssql/TMssqlMetaData.php
+++ b/framework/Data/Common/Mssql/TMssqlMetaData.php
@@ -202,7 +202,7 @@ EOD;
foreach($command->query() as $info)
{
$fkeys[$info['FK_CONSTRAINT_NAME']]['keys'][$info['FK_COLUMN_NAME']] = $info['UQ_COLUMN_NAME'];
- $fkeys[$info['FK_CONSTRAINT_NAME']]['table'] = "{$catalogSchema}.[{$info['UQ_TABLE_NAME']}]";
+ $fkeys[$info['FK_CONSTRAINT_NAME']]['table'] = $info['UQ_TABLE_NAME'];
}
return count($fkeys) > 0 ? array_values($fkeys) : $fkeys;
}
diff --git a/framework/Data/Common/Mysql/TMysqlMetaData.php b/framework/Data/Common/Mysql/TMysqlMetaData.php
index ca1efaaa..ebe851b6 100644
--- a/framework/Data/Common/Mysql/TMysqlMetaData.php
+++ b/framework/Data/Common/Mysql/TMysqlMetaData.php
@@ -7,6 +7,8 @@ Prado::using('System.Data.Common.Mysql.TMysqlTableInfo');
class TMysqlMetaData extends TDbMetaData
{
+ private $_serverVersion=0;
+
/**
* Get the column definitions for given table.
* @param string table name.
@@ -32,6 +34,21 @@ class TMysqlMetaData extends TDbMetaData
}
/**
+ * @return float server version.
+ */
+ protected function getServerVersion()
+ {
+ if(!$this->_serverVersion)
+ {
+ $version = $this->getDbConnection()->getAttribute(PDO::ATTR_SERVER_VERSION);
+ $digits=array();
+ preg_match('/(\d+)\.(\d+)\.(\d+)/', $version, $digits);
+ $this->_serverVersion=floatval($digits[1].'.'.$digits[2].$digits[3]);
+ }
+ return $this->_serverVersion;
+ }
+
+ /**
* @param TMysqlTableInfo table information.
* @param array column information.
*/
@@ -42,7 +59,7 @@ class TMysqlMetaData extends TDbMetaData
$info['ColumnName'] = "`$columnId`"; //quote the column names!
$info['ColumnId'] = $columnId;
$info['ColumnIndex'] = $col['index'];
- if($col['Null']!=='NO')
+ if($col['Null']==='YES')
$info['AllowNull'] = true;
if(is_int(strpos(strtolower($col['Extra']), 'auto_increment')))
$info['AutoIncrement']=true;
@@ -154,7 +171,7 @@ class TMysqlMetaData extends TDbMetaData
* For MySQL version 5.0.1 or later we can use SHOW FULL TABLES
* http://dev.mysql.com/doc/refman/5.0/en/show-tables.html
*
- * For MySQL version 5.0.0 or ealier, this always return false.
+ * For MySQL version 5.0.1 or ealier, this always return false.
* @param string database name, null to use default connection database.
* @param string table or view name.
* @return boolean true if is view, false otherwise.
@@ -162,7 +179,7 @@ class TMysqlMetaData extends TDbMetaData
*/
protected function getIsView($schemaName,$tableName)
{
- if(intval($this->getDbConnection()->getAttribute(PDO::ATTR_SERVER_VERSION))<5)
+ if($this->getServerVersion()<5.01)
return false;
if($schemaName!==null)
$sql = "SHOW FULL TABLES FROM `{$schemaName}` LIKE :table";
@@ -194,12 +211,15 @@ class TMysqlMetaData extends TDbMetaData
$sql = "SHOW INDEX FROM {$table}";
$command = $this->getDbConnection()->createCommand($sql);
$primary = array();
- $foreign = $this->getForeignConstraints($schemaName,$tableName);
foreach($command->query() as $row)
{
if($row['Key_name']==='PRIMARY')
$primary[] = $row['Column_name'];
}
+ if($this->getServerVersion() > 5)
+ $foreign = $this->getForeignConstraints($schemaName,$tableName);
+ else
+ $foreign = $this->findForeignConstraints($schemaName,$tableName);
return array($primary,$foreign);
}
@@ -234,12 +254,53 @@ EOD;
foreach($command->query() as $col)
{
$fkeys[$col['con']]['keys'][$col['col']] = $col['fkcol'];
- $fkeys[$col['con']]['table'] = "`{$col['fkschema']}`.`{$col['fktable']}`";
+ $fkeys[$col['con']]['table'] = $col['fktable'];
}
return count($fkeys) > 0 ? array_values($fkeys) : $fkeys;
}
/**
+ * @param string database name
+ * @param string table name
+ * @return string SQL command to create the table.
+ */
+ protected function getShowCreateTable($schemaName, $tableName)
+ {
+ if($schemaName!==null)
+ $sql = "SHOW CREATE TABLE `{$schemaName}`.`{$tableName}`";
+ else
+ $sql = "SHOW CREATE TABLE `{$tableName}`";
+ $command = $this->getDbConnection()->createCommand($sql);
+ $result = $command->queryRow();
+ return $result['Create Table'];
+ }
+
+ /**
+ * Extract foreign key constraints by extracting the contraints from SHOW CREATE TABLE result.
+ * @param string database name
+ * @param string table name
+ * @return array foreign relationship table name and keys.
+ */
+ protected function findForeignConstraints($schemaName, $tableName)
+ {
+ $sql = $this->getShowCreateTable($schemaName, $tableName);
+ $matches =array();
+ $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+`?([^`]+)`?\s\(([^\)]+)\)/mi';
+ preg_match_all($regexp,$sql,$matches,PREG_SET_ORDER);
+ $foreign = array();
+ foreach($matches as $match)
+ {
+ $fields = array_map('trim',explode(',',str_replace('`','',$match[1])));
+ $fk_fields = array_map('trim',explode(',',str_replace('`','',$match[3])));
+ $keys=array();
+ foreach($fields as $k=>$v)
+ $keys[$v] = $fk_fields[$k];
+ $foreign[] = array('keys' => $keys, 'table' => trim($match[2]));
+ }
+ return $foreign;
+ }
+
+ /**
* @param string column name.
* @param TPgsqlTableInfo table information.
* @return boolean true if column is a foreign key.
diff --git a/framework/Data/Common/Pgsql/TPgsqlMetaData.php b/framework/Data/Common/Pgsql/TPgsqlMetaData.php
index bb03b6cd..c68bbaa0 100644
--- a/framework/Data/Common/Pgsql/TPgsqlMetaData.php
+++ b/framework/Data/Common/Pgsql/TPgsqlMetaData.php
@@ -306,7 +306,7 @@ EOD;
$fkeys = array();
foreach(preg_split('/,\s+/', $matches[3]) as $i => $fkey)
$fkeys[$keys[$i]] = $fkey;
- return array('table' => $matches[2], 'keys' => $fkeys);
+ return array('table' => str_replace('"','',$matches[2]), 'keys' => $fkeys);
}
}
diff --git a/framework/Data/Common/Sqlite/TSqliteMetaData.php b/framework/Data/Common/Sqlite/TSqliteMetaData.php
index 6fcf7b7f..ef33f968 100644
--- a/framework/Data/Common/Sqlite/TSqliteMetaData.php
+++ b/framework/Data/Common/Sqlite/TSqliteMetaData.php
@@ -49,7 +49,7 @@ class TSqliteMetaData extends TDbMetaData
if($column->getIsPrimaryKey())
$primary[] = $col['name'];
}
- $info['TableName'] = $table;
+ $info['TableName'] = $tableName;
if($this->getIsView($tableName))
$info['IsView'] = true;
if(count($columns)===0)
diff --git a/framework/Data/DataGateway/TDataGatewayCommand.php b/framework/Data/DataGateway/TDataGatewayCommand.php
index dbabd2b7..43a57aa7 100644
--- a/framework/Data/DataGateway/TDataGatewayCommand.php
+++ b/framework/Data/DataGateway/TDataGatewayCommand.php
@@ -177,6 +177,18 @@ class TDataGatewayCommand extends TComponent
return $this->onExecuteCommand($command,$command->query());
}
+ public function findAllByIndex($criteria,$fields,$values)
+ {
+ $index = $this->getIndexKeyCondition($fields,$values);
+ if(strlen($where = $criteria->getCondition())>0)
+ $criteria->setCondition("({$index}) AND ({$where})");
+ else
+ $criteria->setCondition($index);
+ $command = $this->getFindCommand($criteria);
+ $this->onCreateCommand($command, $criteria);
+ return $this->onExecuteCommand($command,$command->query());
+ }
+
/**
* @param array multiple primary key values or composite value arrays
* @return integer number of rows affected.
@@ -190,6 +202,14 @@ class TDataGatewayCommand extends TComponent
return $this->onExecuteCommand($command,$command->execute());
}
+ protected function getIndexKeyCondition($fields,$values)
+ {
+ $columns = array();
+ foreach($fields as $field)
+ $columns[] = $this->getTableInfo()->getColumn($field)->getColumnName();
+ return '('.implode(', ',$columns).') IN '.$this->quoteTuple($values);
+ }
+
/**
* Construct a "pk IN ('key1', 'key2', ...)" criteria.
* @param array values for IN predicate
@@ -216,11 +236,7 @@ class TDataGatewayCommand extends TComponent
throw new TDbException('dbtablegateway_pk_value_count_mismatch',
$this->getTableInfo()->getTableFullName());
}
-
- $columns = array();
- foreach($primary as $key)
- $columns[] = $this->getTableInfo()->getColumn($key)->getColumnName();
- return '('.implode(', ',$columns).') IN '.$this->quoteTuple($values);
+ return $this->getIndexKeyCondition($primary, $values);
}
/**
diff --git a/tests/simple_unit/ActiveRecord/ActiveRecordMySql5TestCase.php b/tests/simple_unit/ActiveRecord/ActiveRecordMySql5TestCase.php
index b02de3de..b0d7ccf4 100644
--- a/tests/simple_unit/ActiveRecord/ActiveRecordMySql5TestCase.php
+++ b/tests/simple_unit/ActiveRecord/ActiveRecordMySql5TestCase.php
@@ -1,5 +1,4 @@
-<?php
-
+<?php
Prado::using('System.Data.ActiveRecord.TActiveRecord');
require_once(dirname(__FILE__).'/records/Blogs.php');
@@ -7,7 +6,7 @@ class ActiveRecordMySql5TestCase extends UnitTestCase
{
function setup()
{
- $conn = new TDbConnection('mysql:host=localhost;dbname=ar_test', 'test','test');
+ $conn = new TDbConnection('mysql:host=localhost;dbname=ar_test;port=3307', 'test5','test5');
TActiveRecordManager::getInstance()->setDbConnection($conn);
}
diff --git a/tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php b/tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php
new file mode 100644
index 00000000..6c240b2f
--- /dev/null
+++ b/tests/simple_unit/ActiveRecord/ForeignKeyTestCase.php
@@ -0,0 +1,114 @@
+<?php
+
+Prado::using('System.Data.ActiveRecord.TActiveRecord');
+
+abstract class Mysql4Record extends TActiveRecord
+{
+ protected static $conn;
+
+ public function getDbConnection()
+ {
+ if(self::$conn===null)
+ self::$conn = new TDbConnection('mysql:host=localhost;port=3306;dbname=tests', 'test4', 'test4');
+ return self::$conn;
+ }
+}
+
+class Album extends Mysql4Record
+{
+ public $title;
+
+ public $Tracks = array(self::HAS_MANY, 'Track');
+ public $Artists = array(self::HAS_MANY, 'Artist', 'album_artist');
+
+ public static function finder($class=__CLASS__)
+ {
+ return parent::finder($class);
+ }
+}
+
+class Artist extends Mysql4Record
+{
+ public $name;
+
+ public $Albums = array(self::HAS_MANY, 'Album', 'album_artist');
+
+ public static function finder($class=__CLASS__)
+ {
+ return parent::finder($class);
+ }
+}
+
+class Track extends Mysql4Record
+{
+ public $id;
+ public $song_name;
+ public $album_id; //FK -> Album.id
+
+ public $Album = array(self::BELONGS_TO, 'Album');
+
+ public static function finder($class=__CLASS__)
+ {
+ return parent::finder($class);
+ }
+}
+
+abstract class SqliteRecord extends TActiveRecord
+{
+ protected static $conn;
+
+ public function getDbConnection()
+ {
+ if(self::$conn===null)
+ self::$conn = new TDbConnection('sqlite:'.dirname(__FILE__).'/blog.db');
+ return self::$conn;
+ }
+}
+
+class PostRecord extends SqliteRecord
+{
+ const TABLE='posts';
+ public $post_id;
+ public $author;
+ public $create_time;
+ public $title;
+ public $content;
+ public $status;
+
+ public $authorRecord = array(self::HAS_ONE, 'BlogUserRecord');
+
+ public static function finder($className=__CLASS__)
+ {
+ return parent::finder($className);
+ }
+}
+class BlogUserRecord extends SqliteRecord
+{
+ const TABLE='users';
+ public $username;
+ public $email;
+ public $password;
+ public $role;
+ public $first_name;
+ public $last_name;
+
+ public $posts = array(self::HAS_MANY, 'PostRecord');
+
+ public static function finder($className=__CLASS__)
+ {
+ return parent::finder($className);
+ }
+}
+
+class ForeignKeyTestCase extends UnitTestCase
+{
+ function test()
+ {
+ $album = Album::finder()->withTracks()->findAll();
+ //print_r($album);
+ //print_r(PostRecord::finder()->findAll());
+ //print_r(BlogUserRecord::finder()->with_posts()->findAll());
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/simple_unit/ActiveRecord/RecordEventTestCase.php b/tests/simple_unit/ActiveRecord/RecordEventTestCase.php
index fad54eb0..f6009608 100644
--- a/tests/simple_unit/ActiveRecord/RecordEventTestCase.php
+++ b/tests/simple_unit/ActiveRecord/RecordEventTestCase.php
@@ -9,13 +9,13 @@ class RecordEventTestCase extends UnitTestCase
$conn = new TDbConnection('pgsql:host=localhost;dbname=test', 'test','test');
TActiveRecordManager::getInstance()->setDbConnection($conn);
}
-/*
+
function testFindByPk()
{
$user1 = UserRecord::finder()->findByPk('admin');
$this->assertNotNull($user1);
}
-*/
+
function test_same_data_returns_same_object()
{
$criteria = new TActiveRecordCriteria('username = ?', 'admin');
@@ -23,14 +23,14 @@ class RecordEventTestCase extends UnitTestCase
$finder->OnCreateCommand[] = array($this, 'logger');
$finder->OnExecuteCommand[] = array($this, 'logger');
$user1 = $finder->find($criteria);
- var_dump($user1);
+ //var_dump($user1);
- var_dump(UserRecord::finder()->find($criteria));
+ //var_dump(UserRecord::finder()->find($criteria));
}
function logger($sender, $param)
{
- var_dump($param);
+ //var_dump($param);
}
}
diff --git a/tests/simple_unit/ActiveRecord/blog.db b/tests/simple_unit/ActiveRecord/blog.db
new file mode 100644
index 00000000..30a9cb7a
--- /dev/null
+++ b/tests/simple_unit/ActiveRecord/blog.db
Binary files differ
diff --git a/tests/simple_unit/ActiveRecord/mysql4text.sql b/tests/simple_unit/ActiveRecord/mysql4text.sql
new file mode 100644
index 00000000..4d61bc81
--- /dev/null
+++ b/tests/simple_unit/ActiveRecord/mysql4text.sql
@@ -0,0 +1,52 @@
+CREATE TABLE album (
+ title varchar(100) NOT NULL default '',
+ PRIMARY KEY (title)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE artist (
+ name varchar(25) NOT NULL default '',
+ PRIMARY KEY (name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE album_artists (
+ album_title varchar(100) NOT NULL default '',
+ artist_name varchar(25) NOT NULL default '',
+ PRIMARY KEY (album_title,artist_name),
+ KEY FK_album_artists_2 (artist_name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE track (
+ id int(11) NOT NULL auto_increment,
+ song_name varchar(200) NOT NULL default '',
+ album_id varchar(100) NOT NULL default '',
+ PRIMARY KEY (id),
+ KEY album_id (album_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ALTER TABLE album_artists
+ ADD CONSTRAINT FK_album_artists_2 FOREIGN KEY (artist_name) REFERENCES artist (name),
+ ADD CONSTRAINT FK_album_artists_1 FOREIGN KEY (album_title) REFERENCES album (title);
+
+ALTER TABLE track
+ ADD CONSTRAINT track_ibfk_1 FOREIGN KEY (album_id) REFERENCES album (title);
+
+
+INSERT INTO album (title) VALUES ('Album 1');
+INSERT INTO album (title) VALUES ('Album 2');
+
+INSERT INTO artist (name) VALUES ('Dan');
+INSERT INTO artist (name) VALUES ('Jenny');
+INSERT INTO artist (name) VALUES ('Karl');
+INSERT INTO artist (name) VALUES ('Tom');
+
+INSERT INTO album_artists (album_title, artist_name) VALUES ('Album 1', 'Dan');
+INSERT INTO album_artists (album_title, artist_name) VALUES ('Album 2', 'Dan');
+INSERT INTO album_artists (album_title, artist_name) VALUES ('Album 1', 'Jenny');
+INSERT INTO album_artists (album_title, artist_name) VALUES ('Album 2', 'Karl');
+INSERT INTO album_artists (album_title, artist_name) VALUES ('Album 2', 'Tom');
+
+INSERT INTO track (id, song_name, album_id) VALUES (1, 'Track 1', 'Album 1');
+INSERT INTO track (id, song_name, album_id) VALUES (2, 'Song 2', 'Album 1');
+INSERT INTO track (id, song_name, album_id) VALUES (3, 'Track A', 'Album 2');
+INSERT INTO track (id, song_name, album_id) VALUES (4, 'Track B', 'Album 2');
+INSERT INTO track (id, song_name, album_id) VALUES (5, 'Song 3', 'Album 1'); \ No newline at end of file
diff --git a/tests/simple_unit/DbCommon/Mysql4ColumnTest.php b/tests/simple_unit/DbCommon/Mysql4ColumnTest.php
new file mode 100644
index 00000000..d69f3093
--- /dev/null
+++ b/tests/simple_unit/DbCommon/Mysql4ColumnTest.php
@@ -0,0 +1,254 @@
+<?php
+Prado::using('System.Data.*');
+Prado::using('System.Data.Common.Mysql.TMysqlMetaData');
+
+class Mysql4ColumnTest extends UnitTestCase
+{
+ function create_meta_data()
+ {
+ $conn = new TDbConnection('mysql:host=localhost;dbname=tests;port=3306', 'test4','test4');
+ return new TMysqlMetaData($conn);
+ }
+
+ function test_columns()
+ {
+ $table = $this->create_meta_data()->getTableInfo('table1');
+ $this->assertEqual(count($table->getColumns()), 18);
+
+ $columns['id'] = array(
+ 'ColumnName' => '`id`',
+ 'ColumnSize' => 10,
+ 'ColumnIndex' => 0,
+ 'DbType' => 'int unsigned',
+ 'AllowNull' => false,
+ 'DefaultValue' => TDbTableColumn::UNDEFINED_VALUE,
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => true,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => true,
+ );
+
+ $columns['name'] = array(
+ 'ColumnName' => '`name`',
+ 'ColumnSize' => 45,
+ 'ColumnIndex' => 1,
+ 'DbType' => 'varchar',
+ 'AllowNull' => false,
+ 'DefaultValue' => TDbTableColumn::UNDEFINED_VALUE,
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => true,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field1'] = array(
+ 'ColumnName' => '`field1`',
+ 'ColumnSize' => 4,
+ 'ColumnIndex' => 2,
+ 'DbType' => 'tinyint',
+ 'AllowNull' => false,
+ 'DefaultValue' => '0',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field2_text'] = array(
+ 'ColumnName' => '`field2_text`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 3,
+ 'DbType' => 'text',
+ 'AllowNull' => true,
+ 'DefaultValue' => TDbTableColumn::UNDEFINED_VALUE,
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field3_date'] = array(
+ 'ColumnName' => '`field3_date`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 4,
+ 'DbType' => 'date',
+ 'AllowNull' => true,
+ 'DefaultValue' => '2007-02-25',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field4_float'] = array(
+ 'ColumnName' => '`field4_float`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 5,
+ 'DbType' => 'float',
+ 'AllowNull' => false,
+ 'DefaultValue' => 10,
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field5_float'] = array(
+ 'ColumnName' => '`field5_float`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 6,
+ 'DbType' => 'float',
+ 'AllowNull' => false,
+ 'DefaultValue' => '0.0000',
+ 'NumericPrecision' => 5,
+ 'NumericScale' => 4,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field6_double'] = array(
+ 'ColumnName' => '`field6_double`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 7,
+ 'DbType' => 'double',
+ 'AllowNull' => false,
+ 'DefaultValue' => '0',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field7_datetime'] = array(
+ 'ColumnName' => '`field7_datetime`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 8,
+ 'DbType' => 'datetime',
+ 'AllowNull' => false,
+ 'DefaultValue' => '0000-00-00 00:00:00',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field8_timestamp'] = array(
+ 'ColumnName' => '`field8_timestamp`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 9,
+ 'DbType' => 'timestamp',
+ 'AllowNull' => true,
+ 'DefaultValue' => 'CURRENT_TIMESTAMP',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field9_time'] = array(
+ 'ColumnName' => '`field9_time`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 10,
+ 'DbType' => 'time',
+ 'AllowNull' => false,
+ 'DefaultValue' => '00:00:00',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field10_year'] = array(
+ 'ColumnName' => '`field10_year`',
+ 'ColumnSize' => 4,
+ 'ColumnIndex' => 11,
+ 'DbType' => 'year',
+ 'AllowNull' => false,
+ 'DefaultValue' => '0000',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ );
+
+ $columns['field11_enum'] = array(
+ 'ColumnName' => '`field11_enum`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 12,
+ 'DbType' => 'enum',
+ 'AllowNull' => false,
+ 'DefaultValue' => 'one',
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ 'DbTypeValues' => array('one', 'two', 'three'),
+ );
+
+ $columns['field12_SET'] = array(
+ 'ColumnName' => '`field12_SET`',
+ 'ColumnSize' => null,
+ 'ColumnIndex' => 13,
+ 'DbType' => 'set',
+ 'AllowNull' => false,
+ 'DefaultValue' => TDbTableColumn::UNDEFINED_VALUE,
+ 'NumericPrecision' => null,
+ 'NumericScale' => null,
+ 'IsPrimaryKey' => false,
+ 'IsForeignKey' => false,
+ 'SequenceName' => null,
+ 'AutoIncrement' => false,
+ 'DbTypeValues' => array('blue', 'red', 'green'),
+ );
+
+ $this->assertColumn($columns, $table);
+
+ $this->assertNull($table->getSchemaName());
+ $this->assertEqual('table1', $table->getTableName());
+ $this->assertEqual(array('id', 'name'), $table->getPrimaryKeys());
+ }
+
+ function assertColumn($columns, $table)
+ {
+ foreach($columns as $id=>$asserts)
+ {
+ $column = $table->Columns[$id];
+ foreach($asserts as $property=>$assert)
+ {
+ $ofAssert= var_export($assert,true);
+ $value = $column->{$property};
+ $ofValue = var_export($value, true);
+ $this->assertEqual($value, $assert,
+ "Column [{$id}] {$property} value {$ofValue} did not match {$ofAssert}");
+ }
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/tests/simple_unit/DbCommon/MysqlColumnTest.php b/tests/simple_unit/DbCommon/MysqlColumnTest.php
index 17eb6063..1508f2c6 100644
--- a/tests/simple_unit/DbCommon/MysqlColumnTest.php
+++ b/tests/simple_unit/DbCommon/MysqlColumnTest.php
@@ -1,5 +1,4 @@
-<?php
-
+<?php
Prado::using('System.Data.*');
Prado::using('System.Data.Common.Mysql.TMysqlMetaData');
@@ -7,7 +6,7 @@ class MysqlColumnTest extends UnitTestCase
{
function create_meta_data()
{
- $conn = new TDbConnection('mysql:host=localhost;dbname=tests', 'test','test');
+ $conn = new TDbConnection('mysql:host=localhost;dbname=tests;port=3307', 'test5','test5');
return new TMysqlMetaData($conn);
}