Reuse recursive query for GtasksFilter

gtask_related_email
Alex Baker 6 years ago
parent 7bbc9fe279
commit aaa82447b0

@ -22,11 +22,6 @@ public final class CaldavTaskAdapter extends TaskAdapter {
this.caldavDao = caldavDao; this.caldavDao = caldavDao;
} }
@Override
public int getIndent(TaskContainer task) {
return task.getIndent();
}
@Override @Override
public boolean canMove(ViewHolder sourceVh, ViewHolder targetVh) { public boolean canMove(ViewHolder sourceVh, ViewHolder targetVh) {
TaskContainer source = sourceVh.task; TaskContainer source = sourceVh.task;

@ -21,11 +21,6 @@ public class GoogleTaskManualSortAdapter extends TaskAdapter {
this.googleTaskDao = googleTaskDao; this.googleTaskDao = googleTaskDao;
} }
@Override
public int getIndent(TaskContainer task) {
return task.getIndent();
}
@Override @Override
public boolean canMove(ViewHolder sourceVh, ViewHolder targetVh) { public boolean canMove(ViewHolder sourceVh, ViewHolder targetVh) {
TaskContainer source = sourceVh.task; TaskContainer source = sourceVh.task;

@ -53,7 +53,7 @@ public class TaskAdapter {
} }
public int getIndent(TaskContainer task) { public int getIndent(TaskContainer task) {
return 0; return task.getIndent();
} }
public boolean canMove(ViewHolder source, ViewHolder target) { public boolean canMove(ViewHolder source, ViewHolder target) {

@ -104,7 +104,7 @@ public class TaskAdapterProvider {
} }
private TaskAdapter createGoogleTaskAdapter(GtasksFilter filter) { private TaskAdapter createGoogleTaskAdapter(GtasksFilter filter) {
String query = GtasksFilter.toSubtaskQuery(preferences, filter.getSqlQuery()); String query = GtasksFilter.toManualOrder(filter.getSqlQuery());
filter.setFilterQueryOverride(query); filter.setFilterQueryOverride(query);
return preferences.isManualSort() return preferences.isManualSort()
? new GoogleTaskManualSortAdapter(taskDao, googleTaskDao) ? new GoogleTaskManualSortAdapter(taskDao, googleTaskDao)

@ -51,12 +51,15 @@ public class CaldavFilter extends Filter {
private static QueryTemplate queryTemplate(CaldavCalendar caldavCalendar) { private static QueryTemplate queryTemplate(CaldavCalendar caldavCalendar) {
return new QueryTemplate() return new QueryTemplate()
.join(getJoin()) .join(Join.left(CaldavTask.TABLE, Task.ID.eq(Field.field("caldav_tasks.cd_task"))))
.where( .where(getCriterion(caldavCalendar));
Criterion.and( }
TaskDao.TaskCriteria.activeAndVisible(),
Field.field("caldav_tasks.cd_deleted").eq(0), public static Criterion getCriterion(CaldavCalendar caldavCalendar) {
Field.field("caldav_tasks.cd_calendar").eq(caldavCalendar.getUuid()))); return Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
Field.field("caldav_tasks.cd_deleted").eq(0),
Field.field("caldav_tasks.cd_calendar").eq(caldavCalendar.getUuid()));
} }
private static Map<String, Object> getValuesForNewTask(CaldavCalendar caldavCalendar) { private static Map<String, Object> getValuesForNewTask(CaldavCalendar caldavCalendar) {
@ -106,12 +109,4 @@ public class CaldavFilter extends Filter {
public boolean areContentsTheSame(@NonNull FilterListItem other) { public boolean areContentsTheSame(@NonNull FilterListItem other) {
return calendar.equals(((CaldavFilter) other).calendar); return calendar.equals(((CaldavFilter) other).calendar);
} }
private static Join getJoin() {
return Join.left(CaldavTask.TABLE, Task.ID.eq(Field.field("caldav_tasks.cd_task")));
}
public static String getJoinSql() {
return getJoin().toString();
}
} }

@ -6,9 +6,7 @@ import androidx.annotation.NonNull;
import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Field; import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.OrderType;
import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.HashMap; import java.util.HashMap;
@ -16,7 +14,6 @@ import java.util.Map;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskList; import org.tasks.data.GoogleTaskList;
import org.tasks.preferences.Preferences;
public class GtasksFilter extends Filter { public class GtasksFilter extends Filter {
@ -52,67 +49,36 @@ public class GtasksFilter extends Filter {
icon = list.getIcon(); icon = list.getIcon();
} }
public static String toSubtaskQuery(Preferences preferences, String query) { public static String toManualOrder(String query) {
boolean manualSort = preferences.isManualSort();
String parentSort, childPrimarySort, childSecondarySort;
OrderType sortOrder;
OrderType titleOrder = OrderType.ASC;
if (manualSort) {
parentSort = "google_tasks.gt_order";
childPrimarySort = "p.gt_order";
childSecondarySort = "c.gt_order";
sortOrder = OrderType.ASC;
} else {
int sortMode = preferences.getSortMode();
parentSort = SortHelper.orderSelectForSortTypeRecursive(sortMode)
.replaceFirst("AS .*", "");
childPrimarySort = SortHelper.orderSelectForSortTypeRecursive(sortMode)
.replaceFirst("AS .*", "")
.replaceAll("tasks\\.", "parent_tasks.");
childSecondarySort = SortHelper.orderSelectForSortTypeRecursive(sortMode)
.replaceFirst("AS .*", "")
.replaceAll("tasks\\.", "child_tasks.");
sortOrder = sortMode == SortHelper.SORT_MODIFIED ? OrderType.DESC : OrderType.ASC;
if (preferences.isReverseSort() && sortMode == SortHelper.SORT_ALPHA) {
titleOrder = OrderType.DESC;
}
}
if (preferences.isReverseSort()) {
sortOrder = sortOrder == OrderType.DESC ? OrderType.ASC : OrderType.DESC;
}
query = query =
query.replace( query.replace(
"WHERE", "WHERE",
"JOIN (" "JOIN (SELECT 0 as indent, google_tasks.*, COUNT(c.gt_id) AS children, 0 AS siblings, google_tasks.gt_order AS primary_sort, NULL AS secondary_sort"
+ "SELECT 0 AS indent, google_tasks.*, COUNT(c.gt_id) AS children, 0 AS siblings, " + parentSort + " AS primary_sort, NULL AS secondary_sort, UPPER(tasks.title) AS primary_title, NULL AS secondary_title " + " FROM google_tasks LEFT JOIN google_tasks AS c ON c.gt_parent = google_tasks.gt_task"
+ "FROM google_tasks " + " WHERE google_tasks.gt_parent = 0 GROUP BY google_tasks.gt_task"
+ "LEFT JOIN google_tasks AS c ON c.gt_parent = google_tasks.gt_task " + " UNION SELECT 1 as indent, c.*, 0 AS children, COUNT(s.gt_id) AS siblings, p.gt_order AS primary_sort, c.gt_order AS secondary_sort"
+ "LEFT JOIN tasks ON tasks._id = google_tasks.gt_task " + " FROM google_tasks AS c LEFT JOIN google_tasks AS p ON c.gt_parent = p.gt_task"
+ "WHERE google_tasks.gt_parent = 0 " + " LEFT JOIN tasks ON c.gt_parent = tasks._id"
+ "GROUP BY google_tasks.gt_task " + " LEFT JOIN google_tasks AS s ON s.gt_parent = p.gt_task"
+ "UNION " + " WHERE c.gt_parent > 0 AND ((tasks.completed=0) AND (tasks.deleted=0)"
+ "SELECT 1 AS indent, c.*, 0 AS children, COUNT(s.gt_id) AS siblings, " + childPrimarySort + " AS primary_sort, " + childSecondarySort + " AS secondary_sort, UPPER(parent_tasks.title) AS primary_title, UPPER(child_tasks.title) AS secondary_title " + " AND (tasks.hideUntil<(strftime('%s','now')*1000)))"
+ "FROM google_tasks AS c " + " GROUP BY c.gt_task) as g2 ON g2.gt_id = google_tasks.gt_id WHERE");
+ "LEFT JOIN google_tasks AS p ON c.gt_parent = p.gt_task "
+ "LEFT JOIN tasks AS parent_tasks ON c.gt_parent = parent_tasks._id "
+ "LEFT JOIN tasks AS child_tasks ON c.gt_task = child_tasks._id "
+ "LEFT JOIN google_tasks AS s ON s.gt_parent = p.gt_task "
+ "WHERE c.gt_parent > 0 AND ((parent_tasks.completed=0) AND (parent_tasks.deleted=0) AND (parent_tasks.hideUntil<(strftime('%s','now')*1000))) "
+ "GROUP BY c.gt_task"
+ ") as g2 ON g2.gt_id = google_tasks.gt_id WHERE");
query = query.replaceAll("ORDER BY .*", ""); query = query.replaceAll("ORDER BY .*", "");
query = query + "ORDER BY primary_sort " + sortOrder + ", primary_title " + titleOrder + ", indent ASC" + ", secondary_sort " + sortOrder + ", secondary_title " + titleOrder; query = query + "ORDER BY primary_sort ASC, secondary_sort ASC";
return query; return query;
} }
private static QueryTemplate getQueryTemplate(GoogleTaskList list) { private static QueryTemplate getQueryTemplate(GoogleTaskList list) {
return new QueryTemplate() return new QueryTemplate()
.join(Join.left(GoogleTask.TABLE, Task.ID.eq(Field.field("google_tasks.gt_task")))) .join(Join.left(GoogleTask.TABLE, Task.ID.eq(Field.field("google_tasks.gt_task"))))
.where( .where(getCriterion(list));
Criterion.and( }
TaskDao.TaskCriteria.activeAndVisible(),
Field.field("google_tasks.gt_deleted").eq(0), public static Criterion getCriterion(GoogleTaskList list) {
Field.field("google_tasks.gt_list_id").eq(list.getRemoteId()))); return Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
Field.field("google_tasks.gt_deleted").eq(0),
Field.field("google_tasks.gt_list_id").eq(list.getRemoteId()));
} }
private static Map<String, Object> getValuesForNewTasks(GoogleTaskList list) { private static Map<String, Object> getValuesForNewTasks(GoogleTaskList list) {

@ -136,7 +136,7 @@ public class SubtasksHelper {
if (shouldUseSubtasksFragmentForFilter(filter)) { if (shouldUseSubtasksFragmentForFilter(filter)) {
if (filter instanceof GtasksFilter) { if (filter instanceof GtasksFilter) {
query = GtasksFilter.toSubtaskQuery(preferences, query); query = GtasksFilter.toManualOrder(query);
} else { } else {
TagData tagData = tagDataDao.getTagByName(filter.listingTitle); TagData tagData = tagDataDao.getTagByName(filter.listingTitle);
TaskListMetadata tlm = null; TaskListMetadata tlm = null;

@ -6,6 +6,7 @@ import androidx.room.ColumnInfo;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Ignore; import androidx.room.Ignore;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Table; import com.todoroo.andlib.data.Table;
@Entity(tableName = "caldav_tasks") @Entity(tableName = "caldav_tasks")
@ -15,6 +16,12 @@ public class CaldavTask {
@Deprecated public static final Table TABLE = new Table("caldav_tasks"); @Deprecated public static final Table TABLE = new Table("caldav_tasks");
public static final Property.IntegerProperty PARENT =
new Property.IntegerProperty(TABLE, "cd_parent");
public static final Property.IntegerProperty TASK =
new Property.IntegerProperty(TABLE, "cd_task");
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "cd_id") @ColumnInfo(name = "cd_id")
private long id; private long id;

@ -18,6 +18,12 @@ public class GoogleTask {
public static final Property.IntegerProperty ORDER = public static final Property.IntegerProperty ORDER =
new Property.IntegerProperty(GoogleTask.TABLE, "gt_order"); new Property.IntegerProperty(GoogleTask.TABLE, "gt_order");
public static final Property.IntegerProperty PARENT =
new Property.IntegerProperty(TABLE, "gt_parent");
public static final Property.IntegerProperty TASK =
new Property.IntegerProperty(TABLE, "gt_task");
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "gt_id") @ColumnInfo(name = "gt_id")
private transient long id; private transient long id;

@ -13,7 +13,7 @@ public class TaskContainer {
public int siblings; public int siblings;
public long primarySort; public long primarySort;
public long secondarySort; public long secondarySort;
@Deprecated public int indent; public int indent;
private int targetIndent; private int targetIndent;
public String getTagsString() { public String getTagsString() {
@ -85,9 +85,6 @@ public class TaskContainer {
} }
public int getIndent() { public int getIndent() {
if (googletask != null) {
return getParent() > 0 ? 1 : 0;
}
return indent; return indent;
} }

@ -25,6 +25,7 @@ import com.todoroo.andlib.sql.Field;
import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Order;
import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.astrid.api.CaldavFilter; import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter; import com.todoroo.astrid.api.GtasksFilter;
@ -55,6 +56,7 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
private static final PagedList.Config PAGED_LIST_CONFIG = private static final PagedList.Config PAGED_LIST_CONFIG =
new PagedList.Config.Builder().setPageSize(20).build(); new PagedList.Config.Builder().setPageSize(20).build();
private static final Table RECURSIVE = new Table("recursive_tasks");
private static final Field TASKS = field("tasks.*"); private static final Field TASKS = field("tasks.*");
private static final Field GTASK = field(GTASK_METADATA_JOIN + ".*"); private static final Field GTASK = field(GTASK_METADATA_JOIN + ".*");
private static final Field GEOFENCE = field("geofences.*"); private static final Field GEOFENCE = field("geofences.*");
@ -115,7 +117,8 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
tagsJoinCriterion = tagsJoinCriterion =
Criterion.and(tagsJoinCriterion, field(TAGS_METADATA_JOIN + ".tag_uid").neq(uuid)); Criterion.and(tagsJoinCriterion, field(TAGS_METADATA_JOIN + ".tag_uid").neq(uuid));
} else if (filter instanceof GtasksFilter) { } else if (filter instanceof GtasksFilter) {
if (manualSort) { if (preferences.isManualSort()) {
fields.add(INDENT);
fields.add(CHILDREN); fields.add(CHILDREN);
fields.add(SIBLINGS); fields.add(SIBLINGS);
fields.add(PRIMARY_SORT); fields.add(PRIMARY_SORT);
@ -127,7 +130,9 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
Criterion.and(caldavJoinCriterion, field(CALDAV_METADATA_JOIN + ".cd_calendar").eq(uuid)); Criterion.and(caldavJoinCriterion, field(CALDAV_METADATA_JOIN + ".cd_calendar").eq(uuid));
} }
if (filter instanceof CaldavFilter && atLeastLollipop()) { if (atLeastLollipop()
&& (filter instanceof CaldavFilter
|| (!preferences.isManualSort() && filter instanceof GtasksFilter))) {
// TODO This is in some ways a proof of concept demonstrating a recursive query used to pull // TODO This is in some ways a proof of concept demonstrating a recursive query used to pull
// in CalDAV tasks providing parenting across different sort modes. Tags are implemented // in CalDAV tasks providing parenting across different sort modes. Tags are implemented
// as a subquery, which is ugly, but aggregate recursive queries aren't supported. The // as a subquery, which is ugly, but aggregate recursive queries aren't supported. The
@ -148,26 +153,31 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
String sortSelect = SortHelper.orderSelectForSortTypeRecursive(preferences.getSortMode()); String sortSelect = SortHelper.orderSelectForSortTypeRecursive(preferences.getSortMode());
Order order = SortHelper.orderForSortTypeRecursive(preferences); Order order = SortHelper.orderForSortTypeRecursive(preferences);
String filterSql = filter.getSqlQuery(); Field parent;
QueryTemplate query;
// Remove unwanted join if (filter instanceof CaldavFilter) {
filterSql = filterSql.replace(CaldavFilter.getJoinSql(), ""); query = new QueryTemplate()
.join(Join.left(CaldavTask.TABLE, Task.ID.eq(CaldavTask.TASK)))
.where(CaldavFilter.getCriterion(((CaldavFilter) filter).getCalendar()));
parent = CaldavTask.PARENT;
} else {
query = new QueryTemplate()
.join(Join.left(GoogleTask.TABLE, Task.ID.eq(GoogleTask.TASK)))
.where(GtasksFilter.getCriterion(((GtasksFilter) filter).getList()));
parent = GoogleTask.PARENT;
}
String filterSql = query.toString();
String withClause = "WITH RECURSIVE\n" String withClause = "WITH RECURSIVE\n"
+ " recursive_tasks (task, indent, title, sortField) AS (\n" + " recursive_tasks (task, indent, title, sortField) AS (\n"
+ " SELECT _id, 0 AS sort_indent, UPPER(title) AS sort_title, " + sortSelect + "\n" + " SELECT _id, 0 AS sort_indent, UPPER(title) AS sort_title, " + sortSelect + "\n"
+ " FROM tasks\n" + " FROM tasks\n"
+ " INNER JOIN caldav_tasks ON tasks._id = cd_task\n" + filterSql
+ filterSql + "\n" + " AND " + parent + " = 0\n"
+ " AND cd_parent = 0\n"
+ " UNION ALL\n" + " UNION ALL\n"
+ " SELECT _id, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, " + sortSelect + "\n" + " SELECT _id, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, " + sortSelect + "\n"
+ " FROM tasks\n" + " FROM tasks\n"
+ " INNER JOIN caldav_tasks\n" + " INNER JOIN recursive_tasks ON " + parent + " = recursive_tasks.task\n"
+ " ON tasks._id = caldav_tasks.cd_task\n" + filterSql
+ " INNER JOIN recursive_tasks\n"
+ " ON recursive_tasks.task = caldav_tasks.cd_parent\n"
+ filterSql + "\n"
+ " ORDER BY sort_indent DESC, " + order + "\n" + " ORDER BY sort_indent DESC, " + order + "\n"
+ " )\n"; + " )\n";
@ -176,7 +186,7 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
return Query.select(fields.toArray(new Field[0])) return Query.select(fields.toArray(new Field[0]))
.withQueryTemplate(PermaSql.replacePlaceholdersForQuery(joinedQuery)) .withQueryTemplate(PermaSql.replacePlaceholdersForQuery(joinedQuery))
.withPreClause(withClause) .withPreClause(withClause)
.from(new Table("recursive_tasks")) .from(RECURSIVE)
.toString(); .toString();
} else { } else {
fields.add(TAGS); fields.add(TAGS);

Loading…
Cancel
Save