Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
tag_repository.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20
21#include "database/database.h"
22#include "database/sql_debug.h"
23#include "system/macros.h"
24#include "system/mem_alloc.h"
25
26#include <sqlite3.h>
27
29{
31
32 if(imgid < 0)
33 {
34 // clang-format off
36 "SELECT name FROM data.tags t JOIN main.tagged_images i ON "
37 "i.tagid = t.id WHERE imgid IN "
38 "(SELECT imgid FROM main.selected_images)",
39 -1, &stmt, NULL);
40 // clang-format on
41 }
42 else // single image under mouse cursor
43 {
44 // clang-format off
46 "SELECT name FROM data.tags t JOIN main.tagged_images i ON "
47 "i.tagid = t.id WHERE imgid = ?1",
48 -1, &stmt, NULL);
49 // clang-format on
51 }
52
53 GList *result = NULL;
55 result = g_list_prepend(result, g_strdup((const char *)sqlite3_column_text(stmt, 0)));
57
58 return g_list_reverse(result);
59}
60
61void dt_tag_count_free(gpointer data)
62{
64 if(IS_NULL_PTR(t)) return;
65 dt_free(t->name);
66 dt_free(t);
67}
68
70{
72 // clang-format off
74 "SELECT t.id, t.name, ti.count"
75 " FROM data.tags AS t"
76 " LEFT JOIN (SELECT tagid,"
77 " COUNT(DISTINCT imgid) AS count"
78 " FROM main.tagged_images"
79 " GROUP BY tagid) AS ti"
80 " ON ti.tagid = t.id"
81 " WHERE name = ?1 OR SUBSTR(name, 1, LENGTH(?2)) = ?2",
82 -1, &stmt, NULL);
83 // clang-format on
86
87 GList *tags = NULL;
89 {
90 const char *name = (const char *)sqlite3_column_text(stmt, 1);
92 if(t)
93 {
94 t->id = sqlite3_column_int(stmt, 0);
95 t->name = g_strdup(name ? name : "");
96 t->count = sqlite3_column_int(stmt, 2);
97 tags = g_list_prepend(tags, t);
98 }
99 }
101
102 // Row order, NOT reverse-row: dt_map_location_get_locations_by_path() walks this list and
103 // prepends into its own, and that second prepend is what reproduces the single prepend the
104 // original did straight off the cursor. Returning reverse-row here would flip the result.
105 return g_list_reverse(tags);
106}
107
108
109/* ---------------------------------------------------------------------------------------
110 * Identity and lifecycle
111 * ------------------------------------------------------------------------------------- */
112
113/* Run a one-text-parameter query and return the first integer column, or 0. */
114static guint _first_id_for_text(const char *query, const char *value)
115{
116 if(IS_NULL_PTR(value)) return 0;
117
121
122 guint id = 0;
125 return id;
126}
127
128/* Run a one-integer-parameter statement to completion. */
137
139{
140 return _first_id_for_text("SELECT id FROM data.tags WHERE name = ?1", name);
141}
142
144{
145 // clang-format off
146 return _first_id_for_text("SELECT T.id, T.flags FROM data.tags AS T "
147 "WHERE LOWER(T.name) = LOWER(?1)", name);
148 // clang-format on
149}
150
152{
153 if(IS_NULL_PTR(name)) return 0;
154
156 // clang-format off
158 "INSERT INTO data.tags (id, name) VALUES (NULL, ?1)", -1, &stmt, NULL);
159 // clang-format on
163
164 /* Read the id back rather than taking sqlite3_last_insert_rowid(): that is what this
165 * always did, and the two differ if anything else on this connection inserts in
166 * between. */
168}
169
170gchar *dt_tag_repository_get_name(const guint tagid)
171{
173 // clang-format off
175 "SELECT name FROM data.tags WHERE id= ?1", -1, &stmt, NULL);
176 // clang-format on
178
179 gchar *name = NULL;
180 if(sqlite3_step(stmt) == SQLITE_ROW) name = g_strdup((const char *)sqlite3_column_text(stmt, 0));
182 return name;
183}
184
185void dt_tag_repository_rename(const guint tagid, const char *new_name)
186{
188 // clang-format off
190 "UPDATE data.tags SET name = ?2 WHERE id = ?1", -1, &stmt, NULL);
191 // clang-format on
196}
197
199{
201 // clang-format off
203 "SELECT COUNT(*) FROM main.tagged_images WHERE tagid=?1", -1, &stmt, NULL);
204 // clang-format on
206
207 int count = -1;
210 return count;
211}
212
213void dt_tag_repository_delete(const guint tagid)
214{
215 _run_for_id("DELETE FROM data.tags WHERE id=?1", tagid);
216 _run_for_id("DELETE FROM main.tagged_images WHERE tagid=?1", tagid);
217 _run_for_id("DELETE FROM memory.darktable_tags WHERE tagid=?1", tagid);
218}
219
221{
222 if(IS_NULL_PTR(id_list)) return;
223
225 gchar *query = g_strdup_printf("DELETE FROM data.tags WHERE id IN (%s)", id_list);
229 dt_free(query);
230
231 query = g_strdup_printf("DELETE FROM main.tagged_images WHERE tagid IN (%s)", id_list);
235 dt_free(query);
236}
237
238void dt_tag_repository_mark_internal(const guint tagid)
239{
240 _run_for_id("INSERT INTO memory.darktable_tags (tagid) VALUES (?1)", tagid);
241}
242
244{
245 DT_DEBUG_SQLITE3_EXEC(dt_database_get_sqlite3_global(), "DELETE FROM memory.darktable_tags",
246 NULL, NULL, NULL);
248 /* `%%` is two wildcards, not a printf escape -- this query is never format-expanded.
249 * Two consecutive `%` match exactly what one does, so it is redundant rather than
250 * wrong, and it is kept verbatim so the text is unchanged. */
251 // clang-format off
253 "INSERT INTO memory.darktable_tags (tagid)"
254 " SELECT DISTINCT id"
255 " FROM data.tags"
256 " WHERE name LIKE 'darktable|%%'",
257 -1, &stmt, NULL);
258 // clang-format on
261}
262
263/* ---------------------------------------------------------------------------------------
264 * Flags and synonyms
265 * ------------------------------------------------------------------------------------- */
266
267gint dt_tag_repository_get_flags(const guint tagid)
268{
271 "SELECT flags FROM data.tags WHERE id = ?1", -1, &stmt, NULL);
273
274 gint flags = 0;
277 return flags;
278}
279
280void dt_tag_repository_set_flags(const guint tagid, const gint flags)
281{
284 "UPDATE data.tags SET flags = ?2 WHERE id = ?1", -1, &stmt, NULL);
289}
290
291void dt_tag_repository_update_flags(const guint tagid, const gint set, const gint keep_mask)
292{
294 // clang-format off
296 "UPDATE data.tags SET flags = (IFNULL(flags, 0) & ?3) | ?2 WHERE id = ?1",
297 -1, &stmt, NULL);
298 // clang-format on
301 DT_DEBUG_SQLITE3_BIND_INT(stmt, 3, keep_mask);
304}
305
306gchar *dt_tag_repository_get_synonyms(const guint tagid)
307{
310 "SELECT synonyms FROM data.tags WHERE id = ?1", -1, &stmt, NULL);
312
313 gchar *synonyms = NULL;
315 synonyms = g_strdup((const char *)sqlite3_column_text(stmt, 0));
317 return synonyms;
318}
319
320void dt_tag_repository_set_synonyms(const guint tagid, const char *synonyms)
321{
324 "UPDATE data.tags SET synonyms = ?2 WHERE id = ?1", -1, &stmt, NULL);
329}
330
331/* ---------------------------------------------------------------------------------------
332 * Attachments
333 * ------------------------------------------------------------------------------------- */
334
335gboolean dt_tag_repository_is_attached(const guint tagid, const int32_t imgid)
336{
338 // clang-format off
340 "SELECT imgid FROM main.tagged_images WHERE imgid = ?1 AND tagid = ?2",
341 -1, &stmt, NULL);
342 // clang-format on
345
346 const gboolean attached = (sqlite3_step(stmt) == SQLITE_ROW);
348 return attached;
349}
350
352{
353 GList *ids = NULL;
354 while(sqlite3_step(stmt) == SQLITE_ROW)
357 // built in reverse order by prepending, so un-reverse it -- both callers returned
358 // row order and their consumers walk the list in it
359 return g_list_reverse(ids);
360}
361
363{
366 "SELECT imgid FROM main.tagged_images WHERE tagid = ?1", -1, &stmt, NULL);
368 return _collect_imgids(stmt);
369}
370
372{
373 if(IS_NULL_PTR(imgid_list)) return NULL;
374
376 // clang-format off
377 gchar *query = g_strdup_printf("SELECT imgid FROM main.tagged_images"
378 " WHERE tagid = %d AND imgid IN (%s)", tagid, imgid_list);
379 // clang-format on
381 dt_free(query);
382 return _collect_imgids(stmt);
383}
384
386{
388 // clang-format off
390 "SELECT COUNT(DISTINCT imgid) AS imgnb FROM main.tagged_images WHERE tagid = ?1",
391 -1, &stmt, NULL);
392 // clang-format on
394
395 uint32_t count = 0;
398 return count;
399}
400
401void dt_tag_repository_detach_batch(const int32_t imgid, const char *tagid_list)
402{
403 if(imgid <= 0 || IS_NULL_PTR(tagid_list)) return;
404
406 // clang-format off
407 gchar *query = g_strdup_printf("DELETE FROM main.tagged_images WHERE imgid = %d AND tagid IN (%s)",
408 imgid, tagid_list);
409 // clang-format on
413 dt_free(query);
414}
415
416void dt_tag_repository_attach_batch(const char *values)
417{
418 if(IS_NULL_PTR(values)) return;
419
421 // clang-format off
422 gchar *query = g_strdup_printf("INSERT INTO main.tagged_images (imgid, tagid, position) VALUES %s",
423 values);
424 // clang-format on
428 dt_free(query);
429}
430
431
432/* ---------------------------------------------------------------------------------------
433 * Attached-tag listings
434 * ------------------------------------------------------------------------------------- */
435
436/* Four queries for one question, because the SQL genuinely differs on two axes: one
437 * image (bind an id) versus the selection (join `main.selected_images`), and whether
438 * internal tags are excluded. Indexed [selection][ignore_internal] so the pair of booleans
439 * picks the query rather than four copies of the surrounding code doing it. Prepared per
440 * call: a cached statement here would be shared GUI-thread/worker-thread state with no
441 * lock, and the tag panel and batch tag jobs both come through this. */
442static const char *const _attached_query[2][2] = {
443 /* one image */
444 { "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
445 " COUNT(DISTINCT I.imgid) AS inb"
446 " FROM main.tagged_images AS I"
447 " JOIN data.tags AS T ON T.id = I.tagid"
448 " WHERE I.imgid = ?1"
449 " GROUP BY I.tagid "
450 " ORDER by T.name",
451 "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
452 " COUNT(DISTINCT I.imgid) AS inb"
453 " FROM main.tagged_images AS I"
454 " JOIN data.tags AS T ON T.id = I.tagid"
455 " WHERE I.imgid = ?1 AND T.id NOT IN memory.darktable_tags"
456 " GROUP BY I.tagid "
457 " ORDER by T.name" },
458 /* the selection */
459 { "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
460 " COUNT(DISTINCT I.imgid) AS inb"
461 " FROM main.tagged_images AS I"
462 " JOIN data.tags AS T ON T.id = I.tagid"
463 " JOIN main.selected_images AS S ON S.imgid = I.imgid"
464 " GROUP BY I.tagid "
465 " ORDER by T.name",
466 "SELECT DISTINCT I.tagid, T.name, T.flags, T.synonyms,"
467 " COUNT(DISTINCT I.imgid) AS inb"
468 " FROM main.tagged_images AS I"
469 " JOIN data.tags AS T ON T.id = I.tagid"
470 " JOIN main.selected_images AS S ON S.imgid = I.imgid"
471 " WHERE T.id NOT IN memory.darktable_tags"
472 " GROUP BY I.tagid "
473 " ORDER by T.name" },
474};
475
476/* Fill the fields that come from a row. `leave` and `select` are the caller's. */
478{
479 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
480 if(IS_NULL_PTR(t)) return NULL;
481
482 t->id = sqlite3_column_int(stmt, 0);
483 t->tag = g_strdup((const char *)sqlite3_column_text(stmt, 1));
484 t->flags = sqlite3_column_int(stmt, 2);
485 t->synonym = g_strdup((const char *)sqlite3_column_text(stmt, 3));
486 if(with_count) t->count = sqlite3_column_int(stmt, 4);
487 return t;
488}
489
490GList *dt_tag_repository_get_attached(const int32_t imgid, const gboolean ignore_internal)
491{
492 const int sel = (imgid > 0) ? 0 : 1;
493 const int ign = ignore_internal ? 1 : 0;
494
497 -1, &stmt, NULL);
498 if(IS_NULL_PTR(stmt)) return NULL;
499 if(sel == 0) DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
500
501 GList *tags = NULL;
502 while(sqlite3_step(stmt) == SQLITE_ROW)
503 {
505 if(t) tags = g_list_prepend(tags, t);
506 }
508
509 return g_list_reverse(tags); // the ORDER BY is the point
510}
511
513{
514 if(!(imgid > 0)) return NULL;
515
517 // clang-format off
519 "SELECT DISTINCT T.id, T.name, T.flags, T.synonyms"
520 " FROM data.tags AS T"
521 // tags attached to image(s), not dt tag, ordered by name
522 " JOIN (SELECT DISTINCT I.tagid, T.name"
523 " FROM main.tagged_images AS I"
524 " JOIN data.tags AS T ON T.id = I.tagid"
525 " WHERE I.imgid = ?1 AND T.id NOT IN memory.darktable_tags"
526 " ORDER by T.name) AS T1"
527 // keep also tags in the path to be able to check category in path
528 " ON T.id = T1.tagid"
529 " OR (T.name = SUBSTR(T1.name, 1, LENGTH(T.name))"
530 " AND SUBSTR(T1.name, LENGTH(T.name) + 1, 1) = '|')",
531 -1, &stmt, NULL);
532 // clang-format on
534
535 GList *tags = NULL;
536 while(sqlite3_step(stmt) == SQLITE_ROW)
537 {
539 if(t) tags = g_list_prepend(tags, t);
540 }
542
543 return g_list_reverse(tags);
544}
545
547{
548 if(IS_NULL_PTR(imgid_list)) return NULL;
549
551 char query[256] = { 0 };
552 /* The `IN (%s)` is the caller's id list and the trailing fragment selects the kind.
553 * Both are composed here rather than bound because neither is a value: one is a list of
554 * unknown length, the other a clause. */
555 // clang-format off
556 snprintf(query, sizeof(query), "SELECT DISTINCT T.id"
557 " FROM main.tagged_images AS I"
558 " JOIN data.tags T on T.id = I.tagid"
559 " WHERE I.imgid IN (%s) %s",
561 kind == DT_TAG_KIND_INTERNAL ? "AND T.id IN memory.darktable_tags" :
562 "AND NOT T.id IN memory.darktable_tags");
563 // clang-format on
565
566 GList *tags = NULL;
567 while(sqlite3_step(stmt) == SQLITE_ROW)
570
571 return tags; // prepend-only, as before
572}
573
575{
577
578 /* Select tags that are similar to the keyword and are actually used to tag images*/
579 // clang-format off
581 "INSERT INTO memory.taglist (id, count)"
582 " SELECT tagid, COUNT(*)"
583 " FROM main.tagged_images"
584 " GROUP BY tagid",
585 -1, &stmt, NULL);
586 // clang-format on
589
590 /* Now put all the bits together */
591 // clang-format off
593 "SELECT T.name, T.id, MT.count, CT.imgnb, T.flags, T.synonyms"
594 " FROM data.tags T "
595 " LEFT JOIN memory.taglist MT ON MT.id = T.id "
596 " LEFT JOIN (SELECT tagid, COUNT(DISTINCT imgid) AS imgnb"
597 " FROM main.tagged_images "
598 " WHERE imgid IN (SELECT imgid FROM main.selected_images) GROUP BY tagid) AS CT "
599 " ON CT.tagid = T.id"
600 " WHERE T.id NOT IN memory.darktable_tags "
601 " ORDER BY T.name ",
602 -1, &stmt, NULL);
603 // clang-format on
604
605 GList *tags = NULL;
606 while(sqlite3_step(stmt) == SQLITE_ROW)
607 {
608 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
609 if(t)
610 {
611 t->tag = g_strdup((const char *)sqlite3_column_text(stmt, 0));
612 t->id = sqlite3_column_int(stmt, 1);
613 t->count = sqlite3_column_int(stmt, 2);
614 const uint32_t imgnb = sqlite3_column_int(stmt, 3);
615 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
618 t->flags = sqlite3_column_int(stmt, 4);
619 t->synonym = g_strdup((const char *)sqlite3_column_text(stmt, 5));
620 tags = g_list_prepend(tags, t);
621 }
622 }
624
625 /* memory.taglist is scratch shared by several listings, so it is emptied on the way out
626 * rather than on the way in -- whoever runs next finds it clean. */
627 DT_DEBUG_SQLITE3_EXEC(dt_database_get_sqlite3_global(), "DELETE FROM memory.taglist", NULL, NULL, NULL);
628
629 return g_list_reverse(tags); // the ORDER BY is the point
630}
631
633{
635 /* Tags attached to at least one image of the current collection */
636 // clang-format off
638 "SELECT DISTINCT T.name, T.id"
639 " FROM data.tags T"
640 " JOIN main.tagged_images TI ON TI.tagid = T.id"
641 " WHERE TI.imgid IN (SELECT imgid FROM memory.collected_images)"
642 " AND T.id NOT IN memory.darktable_tags"
643 " ORDER BY T.name",
644 -1, &stmt, NULL);
645 // clang-format on
646
647 GList *tags = NULL;
648 while(sqlite3_step(stmt) == SQLITE_ROW)
649 {
650 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
651 if(t)
652 {
653 t->tag = g_strdup((const char *)sqlite3_column_text(stmt, 0));
654 t->id = sqlite3_column_int(stmt, 1);
655 tags = g_list_prepend(tags, t);
656 }
657 }
659
660 return g_list_reverse(tags); // the ORDER BY is the point
661}
662
663GList *dt_tag_repository_get_names_under(const int32_t imgid, const char *category)
664{
665 if(IS_NULL_PTR(category)) return NULL;
666
668 // clang-format off
670 "SELECT DISTINCT T.name FROM main.tagged_images AS I "
671 "INNER JOIN data.tags AS T "
672 "ON T.id = I.tagid AND SUBSTR(T.name, 1, LENGTH(?2)) = ?2 "
673 "WHERE I.imgid = ?1",
674 -1, &stmt, NULL);
675 // clang-format on
678
679 GList *names = NULL;
680 while(sqlite3_step(stmt) == SQLITE_ROW)
683
684 return g_list_reverse(names); // query order, which is what the caller walked
685}
686
687/* Only select tags that are equal or child to the one we are looking for once. */
688static void _fill_similar_tags(const char *keyword)
689{
690 gchar *keyword_expr = g_strdup_printf("%s|", keyword);
691
693 // clang-format off
695 "INSERT INTO memory.similar_tags (tagid)"
696 " SELECT id"
697 " FROM data.tags"
698 " WHERE name = ?1 OR SUBSTR(name, 1, LENGTH(?2)) = ?2",
699 -1, &stmt, NULL);
700 // clang-format on
705
707}
708
709static void _clear_similar_tags(void)
710{
711 DT_DEBUG_SQLITE3_EXEC(dt_database_get_sqlite3_global(), "DELETE FROM memory.similar_tags",
712 NULL, NULL, NULL);
713}
714
715/* Run a no-parameter query and return its first integer column. */
716static int _scalar(const char *query)
717{
721 const int v = sqlite3_column_int(stmt, 0);
723 return v;
724}
725
726void dt_tag_repository_count_similar(const char *keyword, int *tag_count, int *img_count)
727{
729 *tag_count = 0;
730 *img_count = 0;
731 if(IS_NULL_PTR(keyword)) return;
732
733 _fill_similar_tags(keyword);
734
735 *tag_count = _scalar("SELECT COUNT(DISTINCT tagid) FROM memory.similar_tags");
736 // clang-format off
737 *img_count = _scalar("SELECT COUNT(DISTINCT ti.imgid)"
738 " FROM main.tagged_images AS ti "
739 " JOIN memory.similar_tags AS st"
740 " ON st.tagid = ti.tagid");
741 // clang-format on
742
744}
745
746void dt_tag_repository_get_similar(const char *keyword, GList **tags, GList **imgids)
747{
748 if(IS_NULL_PTR(keyword) || IS_NULL_PTR(tags) || IS_NULL_PTR(imgids)) return;
749
750 _fill_similar_tags(keyword);
751
753 // clang-format off
755 "SELECT ST.tagid, T.name"
756 " FROM memory.similar_tags ST"
757 " JOIN data.tags T"
758 " ON T.id = ST.tagid ",
759 -1, &stmt, NULL);
760 // clang-format on
761 while(sqlite3_step(stmt) == SQLITE_ROW)
762 {
763 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
764 if(t)
765 {
766 t->id = sqlite3_column_int(stmt, 0);
767 t->tag = g_strdup((const char *)sqlite3_column_text(stmt, 1));
768 *tags = g_list_append(*tags, t);
769 }
770 }
772
773 // clang-format off
775 "SELECT DISTINCT ti.imgid"
776 " FROM main.tagged_images AS ti"
777 " JOIN memory.similar_tags AS st"
778 " ON st.tagid = ti.tagid",
779 -1, &stmt, NULL);
780 // clang-format on
781 while(sqlite3_step(stmt) == SQLITE_ROW)
782 *imgids = g_list_append(*imgids, GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
784
786}
787
788
790 const char *recent_tags, const int nb_recent)
791{
793
794 // get attached tags with how many times they are attached in db and on selected images
795 // clang-format off
797 "INSERT INTO memory.taglist (id, count, count2)"
798 " SELECT S.tagid, COUNT(imgid) AS count,"
799 " CASE WHEN count2 IS NULL THEN 0 ELSE count2 END AS count2"
800 " FROM main.tagged_images AS S"
801 " LEFT JOIN ("
802 " SELECT tagid, COUNT(imgid) AS count2"
803 " FROM main.tagged_images"
804 " WHERE imgid IN main.selected_images"
805 " GROUP BY tagid) AS at"
806 " ON at.tagid = S.tagid"
807 " WHERE S.tagid NOT IN memory.darktable_tags"
808 " GROUP BY S.tagid",
809 -1, &stmt, NULL);
810 // clang-format on
813
814 char *query = NULL;
815 if(confidence != 100)
816 query = g_strdup_printf("SELECT td.name, tagid2, t21.count, t21.count2,"
817 " td.flags, td.synonyms FROM ("
818 // get tags with required confidence
819 " SELECT DISTINCT tagid2 FROM ("
820 " SELECT tagid2 FROM ("
821 // get how many times (tag1, tag2) are attached together (c12)
822 " SELECT tagid1, tagid2, count(*) AS c12"
823 " FROM ("
824 " SELECT DISTINCT tagid AS tagid1, imgid FROM main.tagged_images"
825 " JOIN memory.taglist AS t00"
826 " ON t00.id = tagid1 AND t00.count2 > 0) AS t1"
827 " JOIN ("
828 " SELECT DISTINCT tagid AS tagid2, imgid FROM main.tagged_images"
829 " WHERE tagid NOT IN memory.darktable_tags) AS t2"
830 " ON t2.imgid = t1.imgid AND tagid1 != tagid2"
831 " GROUP BY tagid1, tagid2)"
832 " JOIN memory.taglist AS t01"
833 " ON t01.id = tagid1"
834 " JOIN memory.taglist AS t02"
835 " ON t02.id = tagid2"
836 // filter by confidence and reject tags attached on all selected images
837 " WHERE (t01.count-t01.count2) != 0"
838 " AND (100 * c12 / (t01.count-t01.count2) >= %d)"
839 " AND t02.count2 != %d) "
840 " UNION"
841 // get recent list tags
842 " SELECT * FROM ("
843 " SELECT tn.id AS tagid2 FROM data.tags AS tn"
844 " JOIN memory.taglist AS t02"
845 " ON t02.id = tn.id"
846 " WHERE tn.name IN (\'%s\')"
847 // reject tags attached on all selected images and keep the required number
848 " AND t02.count2 != %d LIMIT %d)) "
849 "LEFT JOIN memory.taglist AS t21 "
850 "ON t21.id = tagid2 "
851 "LEFT JOIN data.tags as td ON td.id = tagid2 ",
853 // clang-format on
854 else
855 query = g_strdup_printf("SELECT tn.name, tn.id, count, count2,"
856 " tn.flags, tn.synonyms "
857 // get recent list tags
858 "FROM data.tags AS tn "
859 "JOIN memory.taglist AS t02 "
860 "ON t02.id = tn.id "
861 "WHERE tn.name IN (\'%s\')"
862 // reject tags attached on all selected images and keep the required number
863 " AND t02.count2 != %d LIMIT %d",
865 // clang-format on
867 -1, &stmt, NULL);
868
869 GList *tags = NULL;
870 while(sqlite3_step(stmt) == SQLITE_ROW)
871 {
872 dt_tag_t *t = g_malloc0(sizeof(dt_tag_t));
873 if(t)
874 {
875 t->tag = g_strdup((const char *)sqlite3_column_text(stmt, 0));
876 t->id = sqlite3_column_int(stmt, 1);
877 t->count = sqlite3_column_int(stmt, 2);
878 const uint32_t imgnb = sqlite3_column_int(stmt, 3);
879 t->select = (nb_selected == 0) ? DT_TS_NO_IMAGE :
882 t->flags = sqlite3_column_int(stmt, 4);
883 t->synonym = g_strdup((const char *)sqlite3_column_text(stmt, 5));
884 tags = g_list_prepend(tags, t);
885 }
886 }
887
889
890 DT_DEBUG_SQLITE3_EXEC(dt_database_get_sqlite3_global(), "DELETE FROM memory.taglist", NULL, NULL, NULL);
891 dt_free(query);
892
893 return g_list_reverse(tags); // query order, which is what the caller appended in
894}
895
896gboolean dt_tag_repository_attach(const guint tagid, const int32_t imgid)
897{
899 // clang-format off
901 "INSERT INTO main.tagged_images (tagid, imgid, position)"
902 " VALUES (?1, ?2,"
903 " (SELECT (IFNULL(MAX(position),0) & 0xFFFFFFFF00000000) + (1 << 32)"
904 " FROM main.tagged_images))",
905 -1, &stmt, NULL);
906 // clang-format on
909 const gboolean ok = (sqlite3_step(stmt) == SQLITE_DONE);
911 return ok;
912}
913
915{
916 gboolean tags_agree = TRUE;
917 gboolean categories_agree = TRUE;
918
919 const int count = (int)g_list_length(imgids);
920 char *set = NULL;
921 if(count > 0)
922 {
923 GString *ids = g_string_new(NULL);
924 for(GList *l = imgids; l; l = g_list_next(l))
925 {
926 if(l != imgids) g_string_append_c(ids, ',');
927 g_string_append_printf(ids, "%d", GPOINTER_TO_INT(l->data));
928 }
929 set = g_string_free(ids, FALSE);
930 }
931
932 if(set)
933 {
934 // clang-format off
935 char *query = g_strdup_printf("SELECT flags, COUNT(DISTINCT imgid) "
936 "FROM main.tagged_images "
937 "JOIN data.tags "
938 "ON data.tags.id = main.tagged_images.tagid AND name NOT LIKE 'darktable|%%' "
939 "WHERE imgid in (%s) GROUP BY tagid", set);
940 // clang-format on
943 dt_free(query);
944
945 if(stmt)
946 {
947 while(sqlite3_step(stmt) == SQLITE_ROW)
948 {
950 categories_agree &= (sqlite3_column_int(stmt, 1) == count);
951 else
952 tags_agree &= (sqlite3_column_int(stmt, 1) == count);
953 }
955 }
956 dt_free(set);
957 }
958
961}
962
964{
965 /* Nothing cached any more: every statement in this file is prepared and finalised per
966 * call. Kept because the connection's close order calls every repository's cleanup. */
967}
968
969// clang-format off
970// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
971// vim: shiftwidth=2 expandtab tabstop=2 cindent
972// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
973// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const dt_adaptation_t kind
const int t
const float v
sqlite3 * dt_database_get_sqlite3_global(void)
Definition database.c:3753
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:65
#define dt_free(ptr)
Definition mem_alloc.h:97
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
const char * name
Definition pdf.h:90
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
Checked wrappers around the sqlite3_* calls: trace the statement, assert the return code,...
#define DT_DEBUG_SQLITE3_EXEC(a, b, c, d, e)
Definition sql_debug.h:129
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition sql_debug.h:137
#define DT_DEBUG_SQLITE3_BIND_TEXT(a, b, c, d, e)
Definition sql_debug.h:148
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition sql_debug.h:145
gboolean dt_tag_repository_attach(const guint tagid, const int32_t imgid)
Attach tagid to imgid, at the end of the tag order.
void dt_tag_repository_delete_batch(const char *id_list)
Delete every tag in id_list and its attachments.
void dt_tag_repository_rename(const guint tagid, const char *new_name)
Rename tagid. The caller checks first that the new name is free.
GList * dt_tag_repository_get_with_usage(const uint32_t nb_selected)
Every user tag with how often it is used and how much of the selection carries it.
GList * dt_tag_repository_get_collection_tags(void)
User tags attached to at least one image of the current collection, by name.
static guint _first_id_for_text(const char *query, const char *value)
void dt_tag_repository_get_agreement(GList *imgids, gboolean *same_tags, gboolean *same_categories)
Do all of imgids carry exactly the same tags? The same categories?
gchar * dt_tag_repository_get_name(const guint tagid)
The name of tagid, newly allocated, or NULL.
int dt_tag_repository_count_attachments(const guint tagid)
How many attachment ROWS tagid has, or -1 if the count could not be read. See dt_tag_repository_count...
GList * dt_tag_repository_get_images(const guint tagid)
Images carrying tagid, GINT_TO_POINTER. Free with g_list_free().
static void _run_for_id(const char *query, const guint id)
GList * dt_tag_repository_get_suggestions(const uint32_t nb_selected, const int confidence, const char *recent_tags, const int nb_recent)
Tags worth suggesting for the current selection.
static void _clear_similar_tags(void)
GList * dt_tag_repository_get_by_path_with_counts(const char *path, const char *path_prefix)
Tags at path or below it, each with the number of distinct images carrying it.
static GList * _collect_imgids(sqlite3_stmt *stmt)
GList * dt_tag_repository_get_ids_for_images(const char *imgid_list, const dt_tag_kind_t kind)
Ids of the tags on a set of images, optionally restricted by kind.
void dt_tag_repository_mark_internal(const guint tagid)
Record tagid as an internal tag.
void dt_tag_repository_get_similar(const char *keyword, GList **tags, GList **imgids)
The tags matching keyword and the images carrying any of them.
guint dt_tag_repository_find_by_name(const char *name)
The id of the tag named name, or 0 when there is none.
GList * dt_tag_repository_get_attached_for_export(const int32_t imgid)
Tags attached to imgid for export, plus every ancestor on their paths.
GList * dt_tag_repository_get_names_under(const int32_t imgid, const char *category)
Names of the tags on imgid that start with category.
guint dt_tag_repository_find_by_name_nocase(const char *name)
The id of the tag whose name matches name case-INSENSITIVELY, or 0.
gint dt_tag_repository_get_flags(const guint tagid)
The flags word of tagid, or 0.
void dt_tag_repository_update_flags(const guint tagid, const gint set, const gint keep_mask)
Set the bits in set and clear those absent from keep_mask: flags = (IFNULL(flags,0) & keep_mask) | se...
void dt_tag_repository_count_similar(const char *keyword, int *tag_count, int *img_count)
How many tags match keyword, and how many images carry any of them.
GList * dt_tag_repository_get_images_in_list(const guint tagid, const char *imgid_list)
Images carrying tagid, restricted to imgid_list – a comma-separated list of decimal image ids compose...
void dt_tag_repository_delete(const guint tagid)
Delete tagid, its attachments, and its memory.darktable_tags entry.
void dt_tag_repository_cleanup(void)
Finalise whatever this repository still caches – today, nothing. See dt_colorlabel_repository_cleanup...
void dt_tag_repository_attach_batch(const char *values)
Attach rows given as the VALUES clause of the insert – "(imgid,tagid,pos),…". The position expression...
gchar * dt_tag_repository_get_synonyms(const guint tagid)
The synonyms of tagid, newly allocated, or NULL.
void dt_tag_repository_set_synonyms(const guint tagid, const char *synonyms)
Replace the synonyms of tagid.
static const char *const _attached_query[2][2]
void dt_tag_repository_rebuild_internal(void)
Rebuild the whole internal-tag cache from data.tags.
uint32_t dt_tag_repository_count_distinct_images(const guint tagid)
Distinct images carrying tagid.
static dt_tag_t * _tag_from_row(sqlite3_stmt *stmt, const gboolean with_count)
void dt_tag_repository_set_flags(const guint tagid, const gint flags)
Replace the flags word of tagid.
static int _scalar(const char *query)
static void _fill_similar_tags(const char *keyword)
void dt_tag_count_free(gpointer data)
Release one dt_tag_count_t, name included. Suits g_list_free_full().
gboolean dt_tag_repository_is_attached(const guint tagid, const int32_t imgid)
TRUE when tagid is attached to imgid.
void dt_tag_repository_detach_batch(const int32_t imgid, const char *tagid_list)
Detach every tag in tagid_list from imgid.
GList * dt_tag_repository_get_attached(const int32_t imgid, const gboolean ignore_internal)
Tags attached to one image or to the current selection, ordered by name.
GList * dt_tag_repository_get_attached_names(const int32_t imgid)
Names of the tags attached to an image.
guint dt_tag_repository_insert(const char *name)
Insert a tag named name and return its new id, or 0 on failure.
data.tags and main.tagged_images.
dt_tag_kind_t
@ DT_TAG_KIND_INTERNAL
@ DT_TAG_KIND_ANY
@ DT_TS_ALL_IMAGES
Definition tags.h:76
@ DT_TS_NO_IMAGE
Definition tags.h:74
@ DT_TS_SOME_IMAGES
Definition tags.h:75
@ DT_TF_CATEGORY
Definition tags.h:64