** remove "tags" and "tags_ids" fields from tasks table (used for better performance)

This commit is contained in:
maxpozdeev 2023-03-24 18:19:21 +03:00
parent 3a68339cb9
commit 8815569861
5 changed files with 146 additions and 64 deletions

View file

@ -24,22 +24,14 @@ if (!$listData) {
die("No list found.");
}
$sqlSort = "ORDER BY compl ASC, ";
if($listData['sorting'] == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC";
elseif($listData['sorting'] == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC";
else $sqlSort .= "ow ASC";
$data = DBCore::defaultInstance()->getTasksByListId($listId, '', (int)$listData['sorting']);
$data = array();
$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist WHERE list_id=$listId $sqlSort");
while($r = $q->fetchAssoc())
{
$data[] = $r;
if (_get('format') == 'ical') {
printICal($listData, $data);
}
else {
printCSV($listData, $data);
}
$format = _get('format');
if($format == 'ical') printICal($listData, $data);
else printCSV($listData, $data);
function printCSV(array $listData, array $data)

View file

@ -32,7 +32,7 @@ $feedType = _get('feed');
if($feedType == 'completed') {
$listData['_feed_descr'] = $lang->get('feed_completed_tasks');
fillData( $data, $listId, 'd_completed', 'AND compl=1' );
fillData( $data, $listId, 'd_completed', 'compl=1' );
}
elseif($feedType == 'modified') {
$listData['_feed_descr'] = $lang->get('feed_modified_tasks');
@ -40,13 +40,13 @@ elseif($feedType == 'modified') {
}
elseif($feedType == 'current') {
$listData['_feed_descr'] = $lang->get('feed_new_tasks');
fillData( $data, $listId, 'd_created', 'AND compl=0' );
fillData( $data, $listId, 'd_created', 'compl=0' );
}
elseif($feedType == 'status') {
$listData['_feed_descr'] = $lang->get('feed_tasks');
fillData( $data, $listId, 'd_created', '' );
fillData( $data, $listId, 'd_edited', 'AND compl=0 AND d_edited > d_created' );
fillData( $data, $listId, 'd_completed', 'AND compl=1' );
fillData( $data, $listId, 'd_edited', 'compl=0 AND d_edited > d_created' );
fillData( $data, $listId, 'd_completed', 'compl=1' );
}
else {
$listData['_feed_descr'] = $lang->get('feed_new_tasks');
@ -64,15 +64,14 @@ printRss($data, $listData);
function fillData(array &$data, int $listId, string $field, string $sqlWhere )
{
$tasks = DBCore::defaultInstance()->getTasksByListId($listId, $sqlWhere, "$field DESC", 100);
$lang = Lang::instance();
$db = DBConnection::instance();
$q = $db->dq("SELECT * FROM {$db->prefix}todolist WHERE list_id=$listId $sqlWhere ORDER BY $field DESC LIMIT 100");
while ($r = $q->fetchAssoc())
foreach ($tasks as $r)
{
if ($r['prio'] > 0) {
$r['prio'] = '+'.$r['prio'];
}
$a = array();
$a = array(); //for _descr
$a[] = $lang->get('task'). ": ". $r['title'];
if ($r['prio']) {
$a[] = $lang->get('priority'). ": $r[prio]";

View file

@ -98,10 +98,14 @@ class TasksController extends ApiController {
$t['total'] = 0;
$t['list'] = array();
$q = $db->dq("SELECT todo.*, duedate IS NULL AS ddn
$q = $db->dq("
SELECT todo.*, todo.duedate IS NULL AS ddn, GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags
FROM {$db->prefix}todolist AS todo
LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id
LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id
WHERE $sqlWhereListId $sqlWhere
GROUP BY todo.id $sqlSort");
GROUP BY todo.id $sqlSort
");
while ($r = $q->fetchAssoc())
{
@ -187,6 +191,11 @@ class TasksController extends ApiController {
checkWriteAccess();
$id = (int)$id;
if (!DBCore::defaultInstance()->taskExists($id)) {
$this->response->data = ['total' => 0];
return;
}
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'edit': $this->response->data = $this->editTask($id); break;
@ -258,14 +267,12 @@ class TasksController extends ApiController {
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$oo = $this->prepareTaskRow($r);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $oo);
$t['list'][] = $oo;
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task);
$t['list'][] = $task;
$t['total'] = 1;
return $t;
}
@ -298,14 +305,12 @@ class TasksController extends ApiController {
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$oo = $this->prepareTaskRow($r);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $oo);
$t['list'][] = $oo;
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task);
$t['list'][] = $task;
$t['total'] = 1;
return $t;
}
@ -329,31 +334,26 @@ class TasksController extends ApiController {
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id");
$aTags = $this->prepareTags($tags);
if($aTags) {
$tags = implode(',', $aTags['tags']);
$tags_ids = implode(',',$aTags['ids']);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
}
$db->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,tags=?,tags_ids=?,duedate=?,d_edited=? WHERE id=$id",
array($title, $note, $prio, $tags, $tags_ids, $duedate, time()) );
$db->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,duedate=?,d_edited=? WHERE id=$id",
array($title, $note, $prio, $duedate, time()) );
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
if ($r) {
$t['list'][] = $this->prepareTaskRow($r);
$t['total'] = 1;
}
$task = $this->getTaskRowById($id);
$t['list'][] = $task;
$t['total'] = 1;
return $t;
}
private function moveTask(int $id): ?array
{
$db = DBConnection::instance();
$fromId = (int)($this->req->jsonBody['from'] ?? 0);
$toId = (int)($this->req->jsonBody['to'] ?? 0);
$result = $this->doMoveTask($id, $toId);
$t = array('total' => $result ? 1 : 0);
if ($fromId == -1 && $result && $r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id")) {
$t['list'][] = $this->prepareTaskRow($r);
if ($fromId == -1 && $result) {
$t['list'][] = $this->getTaskRowById($id);
}
return $t;
}
@ -392,8 +392,7 @@ class TasksController extends ApiController {
array($dateCompleted, $date) );
$t = array();
$t['total'] = 1;
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = $this->prepareTaskRow($r);
$t['list'][] = $this->getTaskRowById($id);
return $t;
}
@ -460,6 +459,15 @@ class TasksController extends ApiController {
return $a;
}
private function getTaskRowById(int $id): ?array
{
$r = DBCore::defaultInstance()->getTaskById($id);
if (!$r) {
throw new Exception("Failed to fetch task data");
}
return $this->prepareTaskRow($r);
}
private function prepareTaskRow(array $r): array
{
$lang = Lang::instance();
@ -487,7 +495,7 @@ class TasksController extends ApiController {
'date' => htmlarray($dCreated),
'dateInt' => (int)$r['d_created'],
'dateFull' => htmlarray($dCreatedFull),
'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)),
'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)), //TODO: move preparing of *inlineTitle to js
'dateEdited' => htmlarray($dEdited),
'dateEditedInt' => (int)$r['d_edited'],
'dateEditedFull' => htmlarray($dEditedFull),
@ -501,8 +509,8 @@ class TasksController extends ApiController {
'note' => noteMarkup($r['note']),
'noteText' => (string)$r['note'],
'ow' => (int)$r['ow'],
'tags' => htmlarray($r['tags']),
'tags_ids' => htmlarray($r['tags_ids']),
'tags' => htmlarray($r['tags'] ?? ''),
'tags_ids' => htmlarray($r['tags_ids'] ?? ''),
'duedate' => htmlarray($dueA['formatted']),
'dueClass' => $dueA['class'],
'dueStr' => htmlarray($dueA['str']),

View file

@ -74,5 +74,84 @@ class DBCore
return $listId;
}
public function taskExists(int $id): bool
{
$db = $this->db;
$count = (int) $db->sq("SELECT COUNT(*) FROM {$db->prefix}todolist WHERE id = $id");
return ($count > 0) ? true : false;
}
public function getTaskById(int $id): ?array
{
$db = $this->db;
$r = $db->sqa("
SELECT todo.*, GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags
FROM {$db->prefix}todolist AS todo
LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id
LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id
WHERE todo.id = $id
");
return $r;
}
/**
*
* @param int $listId
* @param string $sqlWhere
* @param int|string $sort
* @param null|int $limit
* @return array
*/
public function getTasksByListId(int $listId, string $sqlWhere, /* int|string */ $sort, ?int $limit = null): array
{
$db = $this->db;
if ($sqlWhere != '') {
$sqlWhere = "AND $sqlWhere";
}
$sqlSort = '';
if (is_int($sort)) {
$sqlSort = "ORDER BY compl ASC, ";
if ($sort == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC"; // byPrio
elseif ($sort == 101) $sqlSort .= "prio ASC, ddn DESC, duedate DESC, ow DESC"; // byPrio (reverse)
elseif ($sort == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC"; // byDueDate
elseif ($sort == 102) $sqlSort .= "ddn DESC, duedate DESC, prio ASC, ow DESC"; // byDueDate (reverse)
elseif ($sort == 3) $sqlSort .= "d_created ASC, prio DESC, ow ASC"; // byDateCreated
elseif ($sort == 103) $sqlSort .= "d_created DESC, prio ASC, ow DESC"; // byDateCreated (reverse)
elseif ($sort == 4) $sqlSort .= "d_edited ASC, prio DESC, ow ASC"; // byDateModified
elseif ($sort == 104) $sqlSort .= "d_edited DESC, prio ASC, ow DESC"; // byDateModified (reverse)
elseif ($sort == 5) $sqlSort .= "title ASC, prio DESC, ow ASC"; // byTitle
elseif ($sort == 105) $sqlSort .= "title DESC, prio ASC, ow DESC"; // byTitle (reverse)
else $sqlSort .= "ow ASC";
}
else if ($sort != '') {
$sqlSort = "ORDER BY $sort";
}
$sqlLimit = '';
if (!is_null($limit)) {
$sqlLimit = "LIMIT $limit";
}
$q = $db->dq("
SELECT todo.*, todo.duedate IS NULL AS ddn, GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags
FROM {$db->prefix}todolist AS todo
LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id
LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id
WHERE todo.list_id = $listId $sqlWhere
GROUP BY todo.id
$sqlSort
$sqlLimit
");
$data = array();
while ($r = $q->fetchAssoc()) {
$data[] = $r;
}
return $data;
}
}

View file

@ -278,8 +278,6 @@ function createMysqlTables($db)
`note` TEXT,
`prio` TINYINT NOT NULL default 0, /* priority -,0,+ */
`ow` INT NOT NULL default 0, /* order weight */
`tags` VARCHAR(600) NOT NULL default '', /* for fast access to task tags */
`tags_ids` VARCHAR(250) NOT NULL default '', /* no more than 22 tags (x11 chars) */
`duedate` DATE default NULL,
PRIMARY KEY(`id`),
KEY(`list_id`),
@ -290,7 +288,7 @@ function createMysqlTables($db)
$db->ex(
"CREATE TABLE {$db->prefix}tags (
`id` INT UNSIGNED NOT NULL auto_increment,
`name` VARCHAR(50) NOT NULL,
`name` VARCHAR(250) NOT NULL default '',
PRIMARY KEY(`id`),
UNIQUE KEY `name` (`name`)
) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ");
@ -333,7 +331,7 @@ function createSqliteTables($db)
id INTEGER PRIMARY KEY,
uuid CHAR(36) NOT NULL,
ow INTEGER NOT NULL default 0,
name VARCHAR(50) NOT NULL,
name VARCHAR(250) NOT NULL,
d_created INTEGER UNSIGNED NOT NULL default 0,
d_edited INTEGER UNSIGNED NOT NULL default 0,
sorting TINYINT UNSIGNED NOT NULL default 0,
@ -357,8 +355,6 @@ function createSqliteTables($db)
note TEXT COLLATE UTF8CI default NULL,
prio TINYINT NOT NULL default 0,
ow INTEGER NOT NULL default 0,
tags VARCHAR(600) NOT NULL default '',
tags_ids VARCHAR(250) NOT NULL default '',
duedate DATE default NULL
) ");
$db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)");
@ -422,6 +418,8 @@ function databaseVersion(Database_Abstract $db): string
$v = '1.4';
if ( !$db->tableExists($db->prefix.'settings') ) return $v;
$v = '1.7';
if ( $db->tableFieldExists($db->prefix.'todolist', 'tags') ) return $v;
$v = '1.8';
return $v;
}
@ -579,7 +577,7 @@ function update_14_17(Database_Abstract $db, $dbtype)
$db->ex("ALTER TABLE {$db->prefix}lists ADD `extra` TEXT");
# increase the length of list and tag name
# (not applicable to sqlite because it uses VARCHAR fields of eny length as TEXT)
# (not applicable to sqlite because it uses VARCHAR fields of any length as TEXT)
$db->ex("ALTER TABLE {$db->prefix}todolist CHANGE `tags` `tags` VARCHAR(2000) NOT NULL default '' ");
$db->ex("ALTER TABLE {$db->prefix}tags CHANGE `name` `name` VARCHAR(250) NOT NULL default '' ");
$db->ex("ALTER TABLE {$db->prefix}lists CHANGE `name` `name` VARCHAR(250) NOT NULL default '' ");
@ -653,7 +651,7 @@ function update_17_18(Database_Abstract $db, $dbtype)
if ($dbtype == 'sqlite')
{
// Use UTF8CI collate. Old sqlite does not support DROP COLUMN
// Use UTF8CI collate. Old sqlite does not support DROP COLUMN (before v3.35.0 2021-03-12)
$db->ex("DROP INDEX todo_list_id");
$db->ex("DROP INDEX todo_uuid");
$db->ex("ALTER TABLE {$db->prefix}todolist RENAME TO {$db->prefix}todolist_old");
@ -670,11 +668,9 @@ function update_17_18(Database_Abstract $db, $dbtype)
note TEXT COLLATE UTF8CI default NULL,
prio TINYINT NOT NULL default 0,
ow INTEGER NOT NULL default 0,
tags VARCHAR(600) NOT NULL default '',
tags_ids VARCHAR(250) NOT NULL default '',
duedate DATE default NULL )"
);
$db->ex("INSERT INTO {$db->prefix}todolist SELECT * FROM {$db->prefix}todolist_old");
$db->ex("INSERT INTO {$db->prefix}todolist SELECT id,uuid,list_id,d_created,d_completed,d_edited,compl,title,note,prio,ow,duedate FROM {$db->prefix}todolist_old");
$db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)");
$db->ex("CREATE UNIQUE INDEX todo_uuid ON {$db->prefix}todolist (uuid)");
$db->ex("DROP TABLE {$db->prefix}todolist_old");
@ -684,12 +680,20 @@ function update_17_18(Database_Abstract $db, $dbtype)
$db->ex(
"CREATE TABLE {$db->prefix}tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(50) NOT NULL DEFAULT '' COLLATE UTF8CI )"
name VARCHAR(250) NOT NULL DEFAULT '' COLLATE UTF8CI )"
);
$db->ex("INSERT INTO {$db->prefix}tags SELECT * FROM {$db->prefix}tags_old");
$db->ex("CREATE INDEX tags_name ON {$db->prefix}tags (name)");
$db->ex("DROP TABLE {$db->prefix}tags_old");
}
else // mysql
{
$db->ex("ALTER TABLE {$db->prefix}todolist DROP COLUMN tags");
$db->ex("ALTER TABLE {$db->prefix}todolist DROP COLUMN tags_ids");
// if mysql db was created in v1.7.x then tags.name field has length of 50 instead of 250
$db->ex("ALTER TABLE {$db->prefix}tags CHANGE `name` `name` VARCHAR(250) NOT NULL default '' ");
}
$db->ex("COMMIT");