Index: dbo_source.php
===================================================================
--- dbo_source.php (revision 3819)
+++ dbo_source.php (working copy)
@@ -385,11 +385,11 @@
} else {
$text = 'query';
}
-
+
if (php_sapi_name() != 'cli') {
print ("
\n{$this->_queriesCnt} {$text} took {$this->_queriesTime} ms\n");
print ("\n| Nr | Query | Error | Affected | Num. rows | Took (ms) |
\n\n\n");
-
+
foreach($log as $k => $i) {
print ("| " . ($k + 1) . " | {$i['query']} | {$i['error']} | {$i['affected']} | {$i['numRows']} | {$i['took']} |
\n");
}
@@ -550,6 +550,10 @@
}
}
}
+
+ // recursive query joins!
+ $this->__generateRecursiveAssociationQuery($model, $queryData, $linkedModels);
+
// Build final query SQL
$query = $this->generateAssociationQuery($model, $null, null, null, null, $queryData, false, $null);
$resultSet = $this->fetchAll($query, $model->cacheQueries, $model->name);
@@ -625,7 +629,9 @@
if (isset($model->{$className}) && is_object($model->{$className})) {
$data = $model->{$className}->afterFind(array(array($key => $results[$i][$key])));
} else {
- $data = $model->{$className}->afterFind(array(array($key => $results[$i][$key])));
+ //DAL: this makes no sense. The 'if' above proves it ...
+ //DAL: $data = $model->{$className}->afterFind(array(array($key => $results[$i][$key])));
+ $data[0][$key]=& $results[$i][$key];
}
$results[$i][$key] = $data[0][$key];
}
@@ -1054,6 +1060,155 @@
}
}
}
+
+ /**
+ * Find the recursive query joins for conditions like A::B::C.fieldname
+ *
+ *@param $model Root of the query.
+ *@param $queryData
+ *@param $linkedModels List of previously linked models.
+ */
+ function __generateRecursiveAssociationQuery(&$model, &$queryData, &$linkedModels) {
+ $data = '';
+ $null = null;
+
+ $loaded_links = array_merge($linkedModels);
+
+ $conditions =& $queryData['conditions'];
+ if (is_array($conditions)) {
+ $condition_keys = $this->conditionKeys($conditions);
+ } else {
+ // cheat: get_escape... to place ticks around the keys then explode them out
+ // while we're here, correct the escaping
+ $conditions = $queryData['conditions'] = $this->__escapeConditionFieldNames($conditions);
+ $condition_keys = explode("`", $conditions);
+ }
+
+ foreach ($condition_keys as $key) {
+ if (strstr($key,'::') !== false) {
+ // it looks close, can we get there from here?
+
+ $condition_key = $key;
+ $pos = strrchr($condition_key,'.');
+ if (false !== $pos) {
+ $condition_key = substr($condition_key, 0, strlen($condition_key)-strlen($pos));
+ }
+ $condition_path = explode('::',$condition_key);
+
+ $link_path = '';
+ $link_key = '';
+ $link_stack = array();
+ $replacements = array();
+ foreach ($condition_path as $path_key => $condition_element) {
+ if ($path_key === 0) {
+ // the first is different. many joins are already there
+ foreach($model->__associations as $type) {
+ foreach($model->{$type} as $assoc => $assocData) {
+ if ($assoc === $condition_element) {
+ $linkModel =& $model->{$assoc};
+ if (!in_array($type . '/' . $condition_element, $loaded_links)) {
+ // not loaded yet
+
+ $external = isset($assocData['external']);
+
+ if ($external) {
+ // external recursive queries are broke!
+ Object::log("external recursive query not supported ($assoc)", LOG_ERROR);
+ return;
+ }
+
+ if ( $type === 'hasMany' ) {
+ // cheat: hasMany acts like hasOne on a nested join
+ $type = 'hasOne';
+ $result = $this->generateAssociationQuery(&$model, &$linkModel, $type, $assoc, $assocData, &$queryData, $external, &$null);
+ }
+ else if ( $type === 'hasAndBelongsToMany') {
+ // these are broke for our use in generateAssociationQuery
+ // permission to modify this behavior might eliminate....
+ Object::log("HABTM recursive query not supported ($assoc)", LOG_ERROR);
+ return;
+ } else {
+ $result = $this->generateAssociationQuery(&$model, &$linkModel, $type, $assoc, $assocData, &$queryData, $external, &$null);
+ if (is_string($result)) {
+ $result = str_replace('{$__cakeID__$}', $model->name . '.' . $model->primaryKey, $result);
+ $result = str_replace('{$__cakeForeignKey__$}', $assoc . '.' . $assocData['foreignKey'], $result);
+ $queryData['joins'][] = " ,($result) {$this->alias} $assoc ";
+ $loaded_links[] = $type . '/' . $assoc;
+ }
+ }
+ }
+ $link_stack[] = $linkModel;
+ $link_path = $assoc;
+ $model->__assocname__ = $model->name;
+ }
+ }
+ }
+ } else {
+ $amodel = $link_stack[count($link_stack)-1];
+ foreach($amodel->__associations as $type) {
+ foreach($amodel->{$type} as $assoc => $assocData) {
+ if ($assoc === $condition_element) {
+ $linkModel =& $amodel->{$assoc};
+ $external = isset($assocData['external']);
+
+ $link_stack[] = $linkModel;
+ $link_path .= '::' . $assoc;
+
+ if ($external) {
+ // external recursive queries are broke!
+ Object::log("external recursive query not supported ($link_path)", LOG_ERROR);
+ return;
+ }
+
+ $replacements[$assoc] = $link_path;
+
+ $save_conditions = $assocData['conditions'];
+ $save_modelname = $amodel->name;
+ if (isset($replacements[$amodel->name])) {
+ $amodel->name = $replacements[$amodel->name];
+ }
+ $amodel->__assocname__ = $amodel->name;
+
+ if (is_string($assocData['conditions'])) {
+ $assocData['conditions'] = $this->__escapeConditionFieldNames($this->__scrubConditionKey($assocData['conditions'], $replacements));
+ } else {
+ $assocData['conditions'] = $this->__conditionKeysToString( (array)($assocData['conditions']), $replacements );
+ }
+
+ if ( $type === 'hasMany' ) {
+ // cheat: hasMany acts like hasOne on a nested join
+ $type = 'hasOne';
+ $result = $this->generateAssociationQuery( &$amodel, &$linkModel, $type, $link_path, $assocData, &$queryData, $external, &$null);
+ } else if ( $type === 'hasAndBelongsToMany') {
+ // these are broke for our use in generateAssociationQuery
+ // permission to modify this behavior might eliminate....
+ Object::log("HABTM recursive query not supported ($link_path)", LOG_ERROR);
+ return;
+ } else {
+ $result = $this->generateAssociationQuery(&$amodel, &$linkModel, $type, $link_path, $assocData, &$queryData, $external, &$null);
+ if (is_string($result)) {
+ $result = str_replace('{$__cakeID__$}', $amodel->__assocname__ . '.' . $amodel->primaryKey, $result);
+ $result = str_replace('{$__cakeForeignKey__$}', $link_path . '.' . $assocData['foreignKey'], $result);
+ $queryData['joins'][] = " ,($result) {$this->alias} $assoc ";
+ //$loaded_links[] = $type . '/' . $link_path;
+ }
+ }
+
+ //restore model state
+ $assocData['conditions'] = $save_conditions;
+ $amodel->name = $save_modelname;
+ }
+ }
+ }
+ }
+ }
+
+ }// check for ->
+
+ }// if is_array
+ return $data;
+ }
+
/**
* Generates and executes an SQL UPDATE statement for given model, fields, and values.
*
@@ -1256,33 +1411,7 @@
if (trim($conditions) == '') {
$conditions = ' 1 = 1';
} else {
- $start = null;
- $end = null;
-
- if (!empty($this->startQuote)) {
- $start = '\\\\' . $this->startQuote . '\\\\';
- }
- $end = $this->endQuote;
-
- if (!empty($this->endQuote)) {
- $end = '\\\\' . $this->endQuote . '\\\\';
- }
- preg_match_all('/(?:\'[^\'\\\]*(?:\\\.[^\'\\\]*)*\')|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $match, PREG_PATTERN_ORDER);
-
- if (isset($match['1']['0'])) {
- $pregCount = count($match['1']);
-
- for($i = 0; $i < $pregCount; $i++) {
- if (!empty($match['1'][$i]) && !is_numeric($match['1'][$i])) {
- $conditions = preg_replace('/^' . $match['0'][$i] . '/', ' '.$this->name($match['1'][$i]), $conditions);
- if (strpos($conditions, '(' . $match['0'][$i]) === false) {
- $conditions = preg_replace('/[^\w]' . $match['0'][$i] . '/', ' '.$this->name($match['1'][$i]), $conditions);
- } else {
- $conditions = preg_replace('/' . $match['0'][$i] . '/', ' '.$this->name($match['1'][$i]), $conditions);
- }
- }
- }
- }
+ $conditions = $this->__escapeConditionFieldNames($conditions);
}
return $clause . $conditions;
} else {
@@ -1295,8 +1424,60 @@
}
}
+ /**
+ * Place DB field name escapes on a condition string.
+ */
+ function __escapeConditionFieldNames($conditions) {
+
+ $start = null;
+ $end = null;
+
+ if (!empty($this->startQuote)) {
+ $start = '\\\\' . $this->startQuote . '\\\\';
+ }
+ $end = $this->endQuote;
+
+ if (!empty($this->endQuote)) {
+ $end = '\\\\' . $this->endQuote . '\\\\';
+ }
+ //DAL: add : as part of linkModel name
+ // preg_match_all('/(?:\'[^\'\\\]*(?:\\\.[^\'\\\]*)*\')|([a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $match, PREG_PATTERN_ORDER);
+ preg_match_all('/(?:\'[^\'\\\]*(?:\\\.[^\'\\\]*)*\')|([\\:a-z0-9_' . $start . $end . ']*\\.[a-z0-9_' . $start . $end . ']*)/i', $conditions, $match, PREG_PATTERN_ORDER);
+
+ if (isset($match['1']['0'])) {
+ $pregCount = count($match['1']);
+ for($i = 0; $i < $pregCount; $i++) {
+ if (!empty($match['1'][$i]) && !is_numeric($match['1'][$i])) {
+ $conditions = preg_replace('/^' . $match['0'][$i] . '/', ' '.$this->name($match['1'][$i]), $conditions);
+ if (strpos($conditions, '(' . $match['0'][$i]) === false) {
+ $conditions = preg_replace('/[^\w]' . $match['0'][$i] . '/', ' '.$this->name($match['1'][$i]), $conditions);
+ } else {
+ $conditions = preg_replace('/' . $match['0'][$i] . '/', ' '.$this->name($match['1'][$i]), $conditions);
+ }
+ }
+ }
+ }
+ return $conditions;
+ }
+
+ /**
+ * Convert a conditions array into a flat list of string conditions.
+ * This one keeps the interface contract.
+ */
function conditionKeysToString($conditions) {
+ $recursivePaths = array();
+ return $this->__conditionKeysToString($conditions, $recursivePaths);
+ }
+ /**
+ * Convert a conditions array into a flat list of string conditions.
+ *
+ * @param $conditions array of query conditions
+ * @param $recursivePaths array optional map for recursive association queries contains SimpleName => FullPathName.
+ */
+ function __conditionKeysToString($conditions, &$recursivePaths) {
+
+
$c = 0;
$data = null;
$out = array();
@@ -1306,7 +1487,7 @@
foreach($conditions as $key => $value) {
if (in_array(strtolower(trim($key)), $bool)) {
$join = ' ' . strtoupper($key) . ' ';
- $value = $this->conditionKeysToString($value);
+ $value = $this->conditionKeysToString($value, recursivePaths);
if (strpos($join, 'NOT')) {
$out[] = 'NOT (' . join(') ' . strtoupper($key) . ' (', $value) . ')';
} else {
@@ -1317,7 +1498,7 @@
$keys = array_keys($value);
if ($keys[0] === 0) {
- $data = $this->name($key) . ' IN (';
+ $data = $this->__scrubConditionKey($this->name($key),$recursivePaths) . ' IN (';
if (strpos($value[0], '-!') === 0){
$value[0] = str_replace('-!', '', $value[0]);
$data .= $value[0];
@@ -1329,14 +1510,14 @@
$data[strlen($data) - 2] = ')';
}
} else {
- $out[] = '(' . join(') AND (', $this->conditionKeysToString($value)) . ')';
+ $out[] = '(' . join(') AND (', $this->conditionKeysToString($value, $recursivePaths)) . ')';
}
} elseif(is_numeric($key)) {
$data = ' ' . $value;
} elseif($value === null) {
- $data = $this->name($key) . ' IS NULL';
+ $data = $this->__scrubConditionKey($this->name($key), $recursivePaths) . ' IS NULL';
} elseif($value === '') {
- $data = $this->name($key) . " = ''";
+ $data = $this->__scrubConditionKey($this->name($key)) . " = ''";
} elseif(preg_match('/^([a-z]*\\([a-z0-9]*\\)\\x20?|(?:like\\x20)|(?:or\\x20)|(?:not\\x20)|(?:in\\x20)|(?:between\\x20)|(?:regexp\\x20)|[<> = !]{1,3}\\x20?)?(.*)/i', $value, $match)) {
if (preg_match('/(\\x20[\\w]*\\x20)/', $key, $regs)) {
$clause = $regs['1'];
@@ -1353,13 +1534,13 @@
if (strpos($match['2'], '-!') === 0) {
$match['2'] = str_replace('-!', '', $match['2']);
- $data = $this->name($key) . ' ' . $match['1'] . ' ' . $match['2'];
+ $data = $this->__scrubConditionKey($this->name($key), $recursivePaths) . ' ' . $match['1'] . ' ' . $match['2'];
} else {
if ($match['2'] != '' && !is_numeric($match['2'])) {
$match['2'] = $this->value($match['2']);
$match['2'] = str_replace(' AND ', "' AND '", $match['2']);
}
- $data = $this->name($key) . ' ' . $match['1'] . ' ' . $match['2'];
+ $data = $this->__scrubConditionKey($this->name($key), $recursivePaths) . ' ' . $match['1'] . ' ' . $match['2'];
}
}
@@ -1371,6 +1552,46 @@
}
return $out;
}
+
+ /**
+ * Place the recursive query paths onto the field names.
+ */
+ function __scrubConditionKey($text, &$recursivePathMap) {
+ foreach ($recursivePathMap as $pathkey => $pathvalue) {
+ $text = str_replace($pathkey, $pathvalue, $text);
+ }
+ return $text;
+ }
+
+ /**
+ * Get the list of condition's key
+ *
+ * @param array $conditions Query conditions
+ * @param array out optional set of keys to add - not typically used by users.
+ * @return array keys
+ */
+ function conditionKeys($conditions, $out=array()) {
+
+ $bool = array('and', 'or', 'and not', 'or not', 'xor', '||', '&&');
+
+ foreach($conditions as $key => $value) {
+ if (in_array(strtolower(trim($key)), $bool)) {
+ $this->conditionKeys($value, $out);
+ } else {
+ if (is_array($value) && !empty($value)) {
+ $keys = array_keys($value);
+ if ($keys[0] !== 0) {
+ $this->conditionKeys($value, $out);
+ }
+ }
+ if (!is_numeric($key)) {
+ $out[] = $key;
+ }
+ }
+ }
+ return $out;
+ }
+
/**
* Returns a limit statement in the correct format for the particular database.
*
@@ -1556,4 +1777,4 @@
}
}
}
-?>
\ No newline at end of file
+?>