mirror of
https://github.com/e107inc/e107.git
synced 2025-08-04 13:47:31 +02:00
Hybrid e_tree_model flat lists or parent-children
e_tree_model is apparently used for flat lists as well as parent-child relationships (trees). Trees are expected to be far smaller than possible flat lists. Very large flat lists (10,000+ rows or greater) are rendered very slowly because of the tree computation overhead. This change figures out whether a flat list or a tree is requested and chooses the appropriate code to run based on what is requested. Trees run the more expensive code while flat lists are returned as-is. In addition, the tree rendering code has been optimized. Optimizations: * Unchanging tree node ID is set once instead of inside a foreach() loop * The parent-child query is now sorted by the sort parent ID so that each move rows to tree nodes iteration doesn't have to run through every remaining row Fixes: #3062
This commit is contained in:
@@ -4364,8 +4364,6 @@ class e_admin_controller_ui extends e_admin_controller
|
|||||||
public function getParentChildQry($orderby=false)
|
public function getParentChildQry($orderby=false)
|
||||||
{
|
{
|
||||||
return "SELECT SQL_CALC_FOUND_ROWS * FROM `#".$this->getTableName()."` ";
|
return "SELECT SQL_CALC_FOUND_ROWS * FROM `#".$this->getTableName()."` ";
|
||||||
// Use the following return statement to break e107 native sorting but speed up tree creation by presorting for e_tree_model::arrayToTree()
|
|
||||||
#return "SELECT SQL_CALC_FOUND_ROWS * FROM `#".$this->getTableName()."` ORDER BY ".$this->getSortParent().", ".$this->getSortField();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -3337,26 +3337,8 @@ class e_tree_model extends e_front_model
|
|||||||
$sql = e107::getDb($this->getParam('model_class', 'e_model'));
|
$sql = e107::getDb($this->getParam('model_class', 'e_model'));
|
||||||
$this->_total = $sql->total_results = false;
|
$this->_total = $sql->total_results = false;
|
||||||
|
|
||||||
// Workaround: Parse and modify db_query param for simulated pagination
|
if($rows = $this->getRows($sql))
|
||||||
$this->prepareSimulatedPagination();
|
|
||||||
// Workaround: Parse and modify db_query param for simulated custom ordering
|
|
||||||
$this->prepareSimulatedCustomOrdering();
|
|
||||||
|
|
||||||
if($sql->gen($this->getParam('db_query'), $this->getParam('db_debug') ? true : false))
|
|
||||||
{
|
{
|
||||||
$rows_tree = self::arrayToTree($sql->rows(),
|
|
||||||
$this->getParam('primary_field'),
|
|
||||||
$this->getParam('sort_parent'));
|
|
||||||
$rows = self::flattenTree($rows_tree,
|
|
||||||
$this->getParam('sort_field'),
|
|
||||||
$this->getParam('sort_order'));
|
|
||||||
|
|
||||||
// Simulated pagination
|
|
||||||
$rows = array_splice($rows,
|
|
||||||
(int) $this->getParam('db_limit_offset'),
|
|
||||||
($this->getParam('db_limit_count') ? $this->getParam('db_limit_count') : count($rows))
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach($rows as $tmp)
|
foreach($rows as $tmp)
|
||||||
{
|
{
|
||||||
$tmp = new $class_name($tmp);
|
$tmp = new $class_name($tmp);
|
||||||
@@ -3387,6 +3369,51 @@ class e_tree_model extends e_front_model
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getRows($sql)
|
||||||
|
{
|
||||||
|
// Tree (Parent-Child Relationship)
|
||||||
|
if ($this->getParam('sort_parent') && $this->getParam('sort_field'))
|
||||||
|
{
|
||||||
|
return $this->getRowsTree($sql);
|
||||||
|
}
|
||||||
|
// Flat List
|
||||||
|
return $this->getRowsList($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRowsList($sql)
|
||||||
|
{
|
||||||
|
$success = $sql->gen($this->getParam('db_query'), $this->getParam('db_debug') ? true : false);
|
||||||
|
if (!$success) return false;
|
||||||
|
|
||||||
|
return $sql->rows();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRowsTree($sql)
|
||||||
|
{
|
||||||
|
// Workaround: Parse and modify db_query param for simulated pagination
|
||||||
|
$this->prepareSimulatedPagination();
|
||||||
|
// Workaround: Parse and modify db_query param for simulated custom ordering
|
||||||
|
$this->prepareSimulatedCustomOrdering();
|
||||||
|
|
||||||
|
$success = $sql->gen($this->getParam('db_query'), $this->getParam('db_debug') ? true : false);
|
||||||
|
if (!$success) return false;
|
||||||
|
|
||||||
|
$rows_tree = self::arrayToTree($sql->rows(),
|
||||||
|
$this->getParam('primary_field'),
|
||||||
|
$this->getParam('sort_parent'));
|
||||||
|
$rows = self::flattenTree($rows_tree,
|
||||||
|
$this->getParam('sort_field'),
|
||||||
|
$this->getParam('sort_order'));
|
||||||
|
|
||||||
|
// Simulated pagination
|
||||||
|
$rows = array_splice($rows,
|
||||||
|
(int) $this->getParam('db_limit_offset'),
|
||||||
|
($this->getParam('db_limit_count') ? $this->getParam('db_limit_count') : count($rows))
|
||||||
|
);
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a relational array with a parent field and a sort order field to a tree
|
* Converts a relational array with a parent field and a sort order field to a tree
|
||||||
* @param array $rows Relational array with a parent field and a sort order field
|
* @param array $rows Relational array with a parent field and a sort order field
|
||||||
@@ -3420,19 +3447,23 @@ class e_tree_model extends e_front_model
|
|||||||
{
|
{
|
||||||
$node = &$nodes[0];
|
$node = &$nodes[0];
|
||||||
array_shift($nodes);
|
array_shift($nodes);
|
||||||
|
$nodeID = (int) $node[$primary_field];
|
||||||
foreach($rows as $key => $row)
|
foreach($rows as $key => $row)
|
||||||
{
|
{
|
||||||
$nodeID = (int) $node[$primary_field];
|
|
||||||
$rowParentID = (int) $row[$sort_parent];
|
$rowParentID = (int) $row[$sort_parent];
|
||||||
if($nodeID === $rowParentID)
|
|
||||||
|
// Note: This optimization only works if the SQL query executed was ordered by the sort parent.
|
||||||
|
if($nodeID !== $rowParentID)
|
||||||
{
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$node['_children'][] = &$row;
|
$node['_children'][] = &$row;
|
||||||
unset($rows[$key]);
|
unset($rows[$key]);
|
||||||
$nodes[] = &$row;
|
$nodes[] = &$row;
|
||||||
unset($row);
|
unset($row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flattens a tree into a depth-first array, sorting each node by a field's values
|
* Flattens a tree into a depth-first array, sorting each node by a field's values
|
||||||
@@ -3569,7 +3600,7 @@ class e_tree_model extends e_front_model
|
|||||||
$db_query = $this->getParam('db_query');
|
$db_query = $this->getParam('db_query');
|
||||||
$db_query = preg_replace_callback('/ORDER BY (?:.+\.)*[\.]*([A-Za-z0-9$_,]+)[ ]*(ASC|DESC)*/i', function($matches)
|
$db_query = preg_replace_callback('/ORDER BY (?:.+\.)*[\.]*([A-Za-z0-9$_,]+)[ ]*(ASC|DESC)*/i', function($matches)
|
||||||
{
|
{
|
||||||
if (isset($matches[1]))
|
if (!empty($matches[1]))
|
||||||
{
|
{
|
||||||
$current_sort_field = $this->getParam('sort_field');
|
$current_sort_field = $this->getParam('sort_field');
|
||||||
if (!empty($current_sort_field))
|
if (!empty($current_sort_field))
|
||||||
@@ -3578,11 +3609,16 @@ class e_tree_model extends e_front_model
|
|||||||
}
|
}
|
||||||
$this->setParam('sort_field', array_map('trim', explode(',', $matches[1])));
|
$this->setParam('sort_field', array_map('trim', explode(',', $matches[1])));
|
||||||
}
|
}
|
||||||
if (isset($matches[2]))
|
if (!empty($matches[2]))
|
||||||
$this->setParam('sort_order',
|
$this->setParam('sort_order',
|
||||||
(0 === strcasecmp($matches[2], 'DESC') ? -1 : 1)
|
(0 === strcasecmp($matches[2], 'DESC') ? -1 : 1)
|
||||||
);
|
);
|
||||||
}, $db_query);
|
|
||||||
|
return "";
|
||||||
|
}, $db_query)
|
||||||
|
// Optimization goes with e_tree_model::moveRowsToTreeNodes()
|
||||||
|
. " ORDER BY " . $this->getParam('sort_parent') . ", " . $this->getParam('sort_field');
|
||||||
|
$this->setParam('db_query', $db_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user