Permalink
Browse files

Implementing new features for queries Warning BC BREAK

  • Loading branch information...
1 parent f2b556e commit 59c2679290cf2cc77b0b1014e1bb965ec5581e76 @jails jails committed Oct 2, 2012
View
@@ -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) {}
}
?>
@@ -8,6 +8,8 @@
namespace lithium\data\collection;
+use lithium\util\Set;
+
class RecordSet extends \lithium\data\Collection {
/**
@@ -19,6 +21,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.
*
@@ -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);
+ }
}
}
@@ -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);
+ }
+ }
}
?>
Oops, something went wrong.

0 comments on commit 59c2679

Please sign in to comment.