Skip to content

Commit

Permalink
Implementing new features for queries Warning BC BREAK
Browse files Browse the repository at this point in the history
  • Loading branch information
jails committed Oct 2, 2012
1 parent f2b556e commit 59c2679
Show file tree
Hide file tree
Showing 14 changed files with 989 additions and 462 deletions.
8 changes: 8 additions & 0 deletions data/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ public function item($model, array $data = array(), array $options = array()) {
unset($options['class']);
return $this->_instance($class, compact('model', 'data') + $options);
}

/**
* Applying a strategy to a `lithium\data\model\Query` object
*
* @param array $options The option array
* @param object $context A query object to configure
*/
public function finalizeQuery($options, $context) {}
}

?>
193 changes: 137 additions & 56 deletions data/collection/RecordSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace lithium\data\collection;

use lithium\util\Set;

class RecordSet extends \lithium\data\Collection {

/**
Expand All @@ -18,6 +20,29 @@ class RecordSet extends \lithium\data\Collection {
*/
protected $_columns = array();

/**
* A recursive array of relation dependencies where key are relations
* and value are arrays with their relation dependencies
*
* @var array
*/
protected $_dependencies = array();

/**
* Precompute index of the main model primary key(s) which allow to find
* values directly is result data without the column name matching process
*
* @var array
*/
protected $_keyIndex = array();

/**
* Precompute the main model name
*
* @var array
*/
protected $_name = null;

/**
* Initializes the record set and uses the database connection to get the column list contained
* in the query that created this object.
Expand All @@ -31,6 +56,11 @@ protected function _init() {
parent::_init();
if ($this->_result) {
$this->_columns = $this->_columnMap();
if ($this->_query) {
$this->_dependencies = Set::expand(Set::normalize(array_keys($this->_columns)));
$this->_name = $this->_query->name();
$this->_keyIndex = $this->_keyIndex($this->_name);
}
}
}

Expand Down Expand Up @@ -67,96 +97,147 @@ protected function _set($data = null, $offset = null, $options = array()) {
return $key !== null ? $this->_data[$key] = $data : $this->_data[] = $data;
}

/**
* Convert a PDO `Result` array to a nested `Record` object
*
* @param array $data 2 dimensional PDO `Result` array
* @return object Returns a `Record` object
*/
protected function _mapRecord($data) {
$options = array('exists' => true);
$relationships = array();
$primary = $this->_model;
$conn = $primary::connection();
$main = $record = array();
$i = 0;

if (!$this->_query) {
return $conn->item($primary, $data, $options + compact('relationships'));
foreach ($this->_keyIndex as $key => $value) {
$main[$key] = $data[$key];
}

$dataMap = array();
$relMap = $this->_query->relationships();
$main = null;

do {
$offset = 0;

if ($i != 0) {
$keys = array();
foreach ($this->_keyIndex as $key => $value) {
$keys[$key] = $data[$key];
}
if ($main != $keys) {
$this->_result->prev();
break;
}
}
foreach ($this->_columns as $name => $fields) {
$fieldCount = count($fields);
$record = array_combine($fields, array_slice($data, $offset, $fieldCount));
$record[$i][$name] = array_combine($fields, array_slice($data, $offset, $fieldCount));
$offset += $fieldCount;

if ($name === 0) {
if ($main && $main != $record) {
$this->_result->prev();
break 2;
}
$main = $record;
continue;
}

if ($relMap[$name]['type'] != 'hasMany') {
$dataMap[$name] = $record;
continue;
}

if (array_filter($record)) {
$dataMap[$name][] = $record;
}
}
$i++;
} while ($data = $this->_result->next());

foreach (array_filter(array_keys($this->_columns)) as $name) {
if (!array_key_exists($name, $dataMap)) {
$dataMap[$name] = array();
}
}
$relMap = $this->_query->relationships();
return $this->_hydrateRecord($this->_dependencies[$this->_name], $primary, $record, 0, $i, $this->_name, $relMap, $conn);
}

foreach ($dataMap as $name => $rel) {
$field = $relMap[$name]['fieldName'];
$relModel = $relMap[$name]['model'];
/**
* Hydrate a 2 dimensional PDO `Result` array
*
* @param array $relations The cascading with relation
* @param string $primary Model classname
* @param array $record Loaded Records
* @param integer $min
* @param integer $max
* @param string $name Alias name
* @param array $relMap The query relationships array
* @param object $conn The connection object
* @return object Returns a `Record` object
*/
protected function _hydrateRecord($relations, $primary, $record, $min, $max, $name, &$relMap, $conn) {
$options = array('exists' => true);

if ($relMap[$name]['type'] == 'hasMany') {
foreach ($rel as &$data) {
$data = $conn->item($relModel, $data, $options);
$count = count($record);
if (!empty($relations)) {
foreach ($relations as $relation => $subrelations) {
$relName = $name . '.' . $relation;
$field = $relMap[$relName]['fieldName'];
$relModel = $relMap[$relName]['model'];
$relPk = $relModel::key();
$index = is_array($relPk) ? false: true;

if ($relMap[$relName]['type'] === 'hasMany') {
$main = null;
$i = $min;
$j = $i + 1;
$main = $relModel::key($record[$i][$relName]);
$rel = array();
while ($j < $max) {
$keys = $relModel::key($record[$j][$relName]);
if ($main != $keys) {
$rel[] = $this->_hydrateRecord($subrelations, $relModel, $record, $i, $j, $relName, $relMap, $conn);
$main = $keys;
$i = $j;
}
$j++;
}
if (array_filter($record[$i][$relName])) {
$rel[] = $this->_hydrateRecord($subrelations, $relModel, $record, $i, $j, $relName, $relMap, $conn);
}
$opts = array('class' => 'set') + $options;
$record[$min][$name][$field] = $conn->item($primary, $rel, $opts);
} else {
$record[$min][$name][$field] = $this->_hydrateRecord($subrelations, $relModel, $record, $min, $max, $relName, $relMap, $conn);
}
$opts = array('class' => 'set');
$relationships[$field] = $conn->item($relModel, $rel, $options + $opts);
continue;
}
$relationships[$field] = $conn->item($relModel, $rel, $options);
}
return $conn->item($primary, $main, $options + compact('relationships'));
return $conn->item($primary, $record[$min][$name], $options);
}

protected function _columnMap() {
if ($this->_query && $map = $this->_query->map()) {
if (isset($map[$this->_query->alias()])) {
$map = array($map[$this->_query->alias()]) + $map;
unset($map[$this->_query->alias()]);
} else {
$map = array(array_shift($map)) + $map;
}
return $map;
}
if (!($model = $this->_model)) {
return array();
}
if (!is_object($this->_query) || !$this->_query->join()) {
$map = $model::connection()->schema($this->_query, $this->_result, $this);
return array_values($map);
$map = $model::connection()->schema($this->_query);
return $map;
}

$model = $this->_model;
$map = $model::connection()->schema($this->_query, $this->_result, $this);
$map = array($map[$this->_query->alias()]) + $map;
unset($map[$this->_query->alias()]);
$map = $model::connection()->schema($this->_query);

return $map;
}

/**
* Result object contain SQL result which are generally a 2 dimentionnal array
* where line are records and columns are fields.
* This function extract from the Result object the index of primary key(s).
*
* @param string $name The name of the relation to retreive the index of
* corresponding primary key(s).
* @return array An array where key are index and value are primary key fieldname
*/
protected function _keyIndex($name) {
if (!($model = $this->_model) || !isset($this->_columns[$name])) {
return array();
}
$index = 0;
foreach ($this->_columns as $key => $value) {
if ($key == $name) {
$flip = array_flip($value);
$keys = $model::meta('key');
if (!is_array($keys)) {
$keys = array($keys);
}
$keys = array_flip($keys);
$keys = array_intersect_key($flip, $keys);
foreach ($keys as &$value) {
$value += $index;
}
return array_flip($keys);
}
$index += count($value);
}
}
}

?>
Loading

0 comments on commit 59c2679

Please sign in to comment.