1
0
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:
Nick Liu
2018-03-06 16:08:11 -06:00
parent 1494c18660
commit 8a675021ff
2 changed files with 73 additions and 39 deletions

View File

@@ -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();
} }

View File

@@ -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);
} }
/** /**