forked from darktable-org/darktable
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase.c
More file actions
5877 lines (5194 loc) · 249 KB
/
database.c
File metadata and controls
5877 lines (5194 loc) · 249 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
This file is part of darktable,
Copyright (C) 2011-2025 darktable developers.
darktable is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
darktable is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with darktable. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common/atomic.h"
#include "common/database.h"
#include "common/darktable.h"
#include "common/datetime.h"
#include "common/debug.h"
#include "common/file_location.h"
#include "common/iop_order.h"
#include "common/styles.h"
#include "common/history.h"
#include "common/metadata.h"
#include "common/metadata.h"
#ifdef HAVE_ICU
#include "common/sqliteicu.h"
#endif
#include "control/conf.h"
#include "control/control.h"
#include <gio/gio.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <sqlite3.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
// NEVER change these; after these versions, NEVER update _create_*_schema(), either
// For consistency and reducing duplication / effort, after these versions, the full schema
// is created by running the upgrade steps.
#define LAST_FULL_DATABASE_VERSION_LIBRARY 55
#define LAST_FULL_DATABASE_VERSION_DATA 10
// You HAVE TO bump THESE versions whenever you add an update branches to _upgrade_*_schema_step()!
#define CURRENT_DATABASE_VERSION_LIBRARY 57
#define CURRENT_DATABASE_VERSION_DATA 13
#define USE_NESTED_TRANSACTIONS
#define MAX_NESTED_TRANSACTIONS 5
/* transaction id */
static dt_atomic_int _trxid;
typedef struct dt_database_t
{
gboolean lock_acquired;
/* data database filename */
gchar *dbfilename_data, *lockfile_data;
/* library database filename */
gchar *dbfilename_library, *lockfile_library;
/* ondisk DB */
sqlite3 *handle;
gchar *error_message, *error_dbfilename;
int error_other_pid;
} dt_database_t;
/* migrates database from old place to new */
static void _database_migrate_to_xdg_structure();
/* delete old mipmaps files */
static void _database_delete_mipmaps_files();
#define _SQLITE3_EXEC(a, b, c, d, e) \
if(sqlite3_exec(a, b, c, d, e) != SQLITE_OK) \
{ \
all_ok = FALSE; \
failing_query = b; \
goto end; \
}
int32_t dt_database_last_insert_rowid(const dt_database_t *db)
{
return (int32_t)sqlite3_last_insert_rowid(db->handle);
}
/* migrate from the legacy db format (with the 'settings' blob) to the
first version this system knows */
static gboolean _migrate_schema(dt_database_t *db, int version)
{
gboolean all_ok = TRUE;
const char *failing_query = NULL;
sqlite3_stmt *stmt;
sqlite3_stmt *innerstmt;
if(version != 36) // if anyone shows up with an older db we can probably add extra code
return FALSE;
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
// remove stuff that is either no longer needed or that got renamed
_SQLITE3_EXEC(db->handle, "DROP TABLE IF EXISTS main.lock", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE IF EXISTS main.settings", NULL, NULL, NULL); // yes, we do this in many
// places. because it's really
// important to not miss it in
// any code path.
_SQLITE3_EXEC(db->handle, "DROP INDEX IF EXISTS main.group_id_index", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP INDEX IF EXISTS main.imgid_index", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE IF EXISTS main.mipmaps", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE IF EXISTS main.mipmap_timestamps", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE IF EXISTS main.dt_migration_table", NULL, NULL, NULL);
// using _create_library_schema() and filling that with the old data doesn't work since we always want to generate
// version 1 tables
////////////////////////////// db_info
_SQLITE3_EXEC(db->handle, "CREATE TABLE main.db_info (key VARCHAR PRIMARY KEY, value VARCHAR)",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "INSERT OR REPLACE INTO main.db_info (key, value) VALUES ('version', 1)",
NULL, NULL, NULL);
////////////////////////////// film_rolls
_SQLITE3_EXEC(db->handle, "CREATE INDEX IF NOT EXISTS main.film_rolls_folder_index ON film_rolls (folder)",
NULL, NULL, NULL);
////////////////////////////// images
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN orientation INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN focus_distance REAL", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN group_id INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN histogram BLOB", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN lightmap BLOB", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN longitude REAL", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN latitude REAL", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN color_matrix BLOB", NULL, NULL, NULL);
// the colorspace as specified in some image types
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN colorspace INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN version INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.images ADD COLUMN max_version INTEGER", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.images SET orientation = -1 WHERE orientation IS NULL", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.images SET focus_distance = -1 WHERE focus_distance IS NULL",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.images SET group_id = id WHERE group_id IS NULL", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.images SET max_version = (SELECT COUNT(*)-1 FROM main.images i WHERE "
"i.filename = main.images.filename AND "
"i.film_id = main.images.film_id) WHERE max_version IS NULL",
NULL, NULL, NULL);
_SQLITE3_EXEC(
db->handle,
"UPDATE main.images SET version = (SELECT COUNT(*) FROM main.images i "
"WHERE i.filename = main.images.filename AND "
"i.film_id = main.images.film_id AND i.id < main.images.id) WHERE version IS NULL",
NULL, NULL, NULL);
// make sure we have AUTOINCREMENT on imgid --> move the whole thing away and recreate the table :(
_SQLITE3_EXEC(db->handle, "ALTER TABLE main.images RENAME TO dt_migration_table", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP INDEX IF EXISTS main.images_group_id_index", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP INDEX IF EXISTS main.images_film_id_index", NULL, NULL, NULL);
_SQLITE3_EXEC(
db->handle,
"CREATE TABLE main.images (id INTEGER PRIMARY KEY AUTOINCREMENT, group_id INTEGER, film_id INTEGER, "
"width INTEGER, height INTEGER, filename VARCHAR, maker VARCHAR, model VARCHAR, "
"lens VARCHAR, exposure REAL, aperture REAL, iso REAL, focal_length REAL, "
"focus_distance REAL, datetime_taken CHAR(20), flags INTEGER, "
"output_width INTEGER, output_height INTEGER, crop REAL, "
"raw_parameters INTEGER, raw_denoise_threshold REAL, "
"raw_auto_bright_threshold REAL, raw_black INTEGER, raw_maximum INTEGER, "
"caption VARCHAR, description VARCHAR, license VARCHAR, sha1sum CHAR(40), "
"orientation INTEGER, histogram BLOB, lightmap BLOB, longitude REAL, "
"latitude REAL, color_matrix BLOB, colorspace INTEGER, version INTEGER, max_version INTEGER)",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "CREATE INDEX main.images_group_id_index ON images (group_id)", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "CREATE INDEX main.images_film_id_index ON images (film_id)", NULL, NULL, NULL);
_SQLITE3_EXEC(
db->handle,
"INSERT INTO main.images (id, group_id, film_id, width, height, filename, maker, model, "
"lens, exposure, aperture, iso, focal_length, focus_distance, datetime_taken, flags, "
"output_width, output_height, crop, raw_parameters, raw_denoise_threshold, "
"raw_auto_bright_threshold, raw_black, raw_maximum, caption, description, license, sha1sum, "
"orientation, histogram, lightmap, longitude, latitude, color_matrix, colorspace, version, "
"max_version) "
"SELECT id, group_id, film_id, width, height, filename, maker, model, lens, exposure, aperture, iso, "
"focal_length, focus_distance, datetime_taken, flags, output_width, output_height, crop, "
"raw_parameters, raw_denoise_threshold, raw_auto_bright_threshold, raw_black, raw_maximum, "
"caption, description, license, sha1sum, orientation, histogram, lightmap, longitude, "
"latitude, color_matrix, colorspace, version, max_version FROM dt_migration_table",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE dt_migration_table", NULL, NULL, NULL);
////////////////////////////// selected_images
// selected_images should have a primary key. add it if it's missing:
_SQLITE3_EXEC(db->handle, "CREATE TEMPORARY TABLE dt_migration_table (imgid INTEGER)", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "INSERT INTO dt_migration_table SELECT imgid FROM main.selected_images", NULL, NULL,
NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE main.selected_images", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "CREATE TABLE main.selected_images (imgid INTEGER PRIMARY KEY)", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "INSERT OR IGNORE INTO main.selected_images SELECT imgid FROM dt_migration_table",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE dt_migration_table", NULL, NULL, NULL);
////////////////////////////// history
sqlite3_exec(db->handle, "ALTER TABLE main.history ADD COLUMN blendop_params BLOB", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.history ADD COLUMN blendop_version INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.history ADD COLUMN multi_priority INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.history ADD COLUMN multi_name VARCHAR(256)", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "CREATE INDEX IF NOT EXISTS main.history_imgid_index ON history (imgid)", NULL, NULL,
NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.history SET blendop_version = 1 WHERE blendop_version IS NULL", NULL,
NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.history SET multi_priority = 0 WHERE multi_priority IS NULL", NULL, NULL,
NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.history SET multi_name = ' ' WHERE multi_name IS NULL", NULL, NULL, NULL);
////////////////////////////// mask
_SQLITE3_EXEC(db->handle, "CREATE TABLE IF NOT EXISTS main.mask (imgid INTEGER, formid INTEGER, form INTEGER, "
"name VARCHAR(256), version INTEGER, "
"points BLOB, points_count INTEGER, source BLOB)",
NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.mask ADD COLUMN source BLOB", NULL, NULL,
NULL); // in case the table was there already but missed that column
////////////////////////////// tagged_images
_SQLITE3_EXEC(db->handle, "CREATE INDEX IF NOT EXISTS main.tagged_images_tagid_index ON tagged_images (tagid)",
NULL, NULL, NULL);
////////////////////////////// styles
_SQLITE3_EXEC(db->handle,
"CREATE TABLE IF NOT EXISTS main.styles (id INTEGER, name VARCHAR, description VARCHAR)", NULL,
NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.styles ADD COLUMN id INTEGER", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.styles SET id = rowid WHERE id IS NULL", NULL, NULL, NULL);
////////////////////////////// style_items
_SQLITE3_EXEC(db->handle, "CREATE TABLE IF NOT EXISTS main.style_items (styleid INTEGER, num INTEGER, module "
"INTEGER, operation VARCHAR(256), op_params BLOB, "
"enabled INTEGER, blendop_params BLOB, blendop_version INTEGER, multi_priority "
"INTEGER, multi_name VARCHAR(256))",
NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.style_items ADD COLUMN blendop_params BLOB", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.style_items ADD COLUMN blendop_version INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.style_items ADD COLUMN multi_priority INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.style_items ADD COLUMN multi_name VARCHAR(256)", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.style_items SET blendop_version = 1 WHERE blendop_version IS NULL", NULL,
NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.style_items SET multi_priority = 0 WHERE multi_priority IS NULL", NULL,
NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.style_items SET multi_name = ' ' WHERE multi_name IS NULL", NULL, NULL,
NULL);
////////////////////////////// color_labels
// color_labels could have a PRIMARY KEY that we don't want
_SQLITE3_EXEC(db->handle, "CREATE TEMPORARY TABLE dt_migration_table (imgid INTEGER, color INTEGER)", NULL,
NULL, NULL);
_SQLITE3_EXEC(db->handle, "INSERT INTO dt_migration_table SELECT imgid, color FROM main.color_labels", NULL,
NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE main.color_labels", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "CREATE TABLE main.color_labels (imgid INTEGER, color INTEGER)", NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "CREATE UNIQUE INDEX main.color_labels_idx ON color_labels (imgid, color)", NULL, NULL,
NULL);
_SQLITE3_EXEC(db->handle, "INSERT OR IGNORE INTO main.color_labels SELECT imgid, color FROM dt_migration_table",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "DROP TABLE dt_migration_table", NULL, NULL, NULL);
////////////////////////////// meta_data
_SQLITE3_EXEC(db->handle, "CREATE TABLE IF NOT EXISTS main.meta_data (id INTEGER, key INTEGER, value VARCHAR)",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "CREATE INDEX IF NOT EXISTS main.metadata_index ON meta_data (id, key)", NULL, NULL,
NULL);
////////////////////////////// presets
_SQLITE3_EXEC(db->handle, "CREATE TABLE IF NOT EXISTS main.presets (name VARCHAR, description VARCHAR, "
"operation VARCHAR, op_version INTEGER, op_params BLOB, "
"enabled INTEGER, blendop_params BLOB, blendop_version INTEGER, multi_priority "
"INTEGER, multi_name VARCHAR(256), "
"model VARCHAR, maker VARCHAR, lens VARCHAR, iso_min REAL, iso_max REAL, "
"exposure_min REAL, exposure_max REAL, "
"aperture_min REAL, aperture_max REAL, focal_length_min REAL, focal_length_max "
"REAL, writeprotect INTEGER, "
"autoapply INTEGER, filter INTEGER, def INTEGER, isldr INTEGER)",
NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.presets ADD COLUMN op_version INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.presets ADD COLUMN blendop_params BLOB", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.presets ADD COLUMN blendop_version INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.presets ADD COLUMN multi_priority INTEGER", NULL, NULL, NULL);
sqlite3_exec(db->handle, "ALTER TABLE main.presets ADD COLUMN multi_name VARCHAR(256)", NULL, NULL, NULL);
// the unique index only works if the db doesn't have any (name, operation, op_version) more than once.
// apparently there are dbs out there which do have that. :(
sqlite3_prepare_v2(db->handle,
"SELECT p.rowid, p.name, p.operation, p.op_version FROM main.presets p INNER JOIN "
"(SELECT * FROM (SELECT rowid, name, operation, op_version, COUNT(*) AS count "
"FROM main.presets GROUP BY name, operation, op_version) WHERE count > 1) s "
"ON p.name = s.name AND p.operation = s.operation AND p.op_version = s.op_version",
-1, &stmt, NULL);
// clang-format on
char *last_name = NULL, *last_operation = NULL;
int last_op_version = 0;
int i = 0;
while(sqlite3_step(stmt) == SQLITE_ROW)
{
const int rowid = sqlite3_column_int(stmt, 0);
const char *name = (const char *)sqlite3_column_text(stmt, 1);
const char *operation = (const char *)sqlite3_column_text(stmt, 2);
const int op_version = sqlite3_column_int(stmt, 3);
// is it still the same (name, operation, op_version) triple?
if(!last_name || strcmp(last_name, name) || !last_operation || strcmp(last_operation, operation)
|| last_op_version != op_version)
{
g_free(last_name);
g_free(last_operation);
last_name = g_strdup(name);
last_operation = g_strdup(operation);
last_op_version = op_version;
i = 0;
}
// find the next free amended version of name
// clang-format off
sqlite3_prepare_v2(db->handle, "SELECT name FROM main.presets WHERE name = ?1 || ' (' || ?2 || ')' AND "
"operation = ?3 AND op_version = ?4",
-1, &innerstmt, NULL);
// clang-format on
while(1)
{
sqlite3_bind_text(innerstmt, 1, name, -1, SQLITE_TRANSIENT);
sqlite3_bind_int(innerstmt, 2, i);
sqlite3_bind_text(innerstmt, 3, operation, -1, SQLITE_TRANSIENT);
sqlite3_bind_int(innerstmt, 4, op_version);
if(sqlite3_step(innerstmt) != SQLITE_ROW) break;
sqlite3_reset(innerstmt);
sqlite3_clear_bindings(innerstmt);
i++;
}
sqlite3_finalize(innerstmt);
// rename preset
// clang-format off
const char *query = "UPDATE main.presets SET name = name || ' (' || ?1 || ')' WHERE rowid = ?2";
// clang-format on
sqlite3_prepare_v2(db->handle, query, -1, &innerstmt, NULL);
sqlite3_bind_int(innerstmt, 1, i);
sqlite3_bind_int(innerstmt, 2, rowid);
if(sqlite3_step(innerstmt) != SQLITE_DONE)
{
all_ok = FALSE;
failing_query = query;
goto end;
}
sqlite3_finalize(innerstmt);
}
sqlite3_finalize(stmt);
g_free(last_name);
g_free(last_operation);
// now we should be able to create the index
// clang-format off
_SQLITE3_EXEC(db->handle,
"CREATE UNIQUE INDEX IF NOT EXISTS main.presets_idx ON presets (name, operation, op_version)",
NULL, NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.presets SET blendop_version = 1 WHERE blendop_version IS NULL", NULL,
NULL, NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.presets SET multi_priority = 0 WHERE multi_priority IS NULL", NULL, NULL,
NULL);
_SQLITE3_EXEC(db->handle, "UPDATE main.presets SET multi_name = ' ' WHERE multi_name IS NULL", NULL, NULL, NULL);
// clang-format on
// There are systems where absolute paths don't start with '/' (like Windows).
// Since the bug which introduced absolute paths to the db was fixed before a
// Windows build was available this shouldn't matter though.
// clang-format off
sqlite3_prepare_v2(db->handle, "SELECT id, filename FROM main.images WHERE filename LIKE '/%'", -1, &stmt, NULL);
sqlite3_prepare_v2(db->handle, "UPDATE main.images SET filename = ?1 WHERE id = ?2", -1, &innerstmt, NULL);
// clang-format on
while(sqlite3_step(stmt) == SQLITE_ROW)
{
const int id = sqlite3_column_int(stmt, 0);
const char *path = (const char *)sqlite3_column_text(stmt, 1);
gchar *filename = g_path_get_basename(path);
sqlite3_bind_text(innerstmt, 1, filename, -1, SQLITE_TRANSIENT);
sqlite3_bind_int(innerstmt, 2, id);
sqlite3_step(innerstmt);
sqlite3_reset(innerstmt);
sqlite3_clear_bindings(innerstmt);
g_free(filename);
}
sqlite3_finalize(stmt);
sqlite3_finalize(innerstmt);
// We used to insert datetime_taken entries with '-' as date separators. Since that doesn't work well with
// the regular ':' when parsing
// or sorting we changed it to ':'. This takes care to change what we have as leftovers
// clang-format off
_SQLITE3_EXEC(
db->handle,
"UPDATE main.images SET datetime_taken = REPLACE(datetime_taken, '-', ':') WHERE datetime_taken LIKE '%-%'",
NULL, NULL, NULL);
// clang-format on
end:
if(all_ok)
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
else
{
dt_print(DT_DEBUG_ALWAYS, "[init] failing query: `%s' sql: '%s", failing_query, sqlite3_errmsg(db->handle));
sqlite3_exec(db->handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
}
return all_ok;
}
#undef _SQLITE3_EXEC
#define TRY_EXEC(_query, _message) \
do \
{ \
if(sqlite3_exec(db->handle, _query, NULL, NULL, NULL) != SQLITE_OK) \
{ \
dt_print(DT_DEBUG_ALWAYS, "TRY_EXEC '%s' sql: '%s'", _message, sqlite3_errmsg(db->handle)); \
FINALIZE; \
sqlite3_exec(db->handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL); \
return version; \
} \
} while(0)
#define TRY_STEP(_stmt, _expected, _message) \
do \
{ \
if(sqlite3_step(_stmt) != _expected) \
{ \
dt_print(DT_DEBUG_ALWAYS, "TRY_STEP '%s' sql: '%s'", _message, sqlite3_errmsg(db->handle)); \
FINALIZE; \
sqlite3_exec(db->handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL); \
return version; \
} \
} while(0)
#define TRY_PREPARE(_stmt, _query, _message) \
do \
{ \
if(sqlite3_prepare_v2(db->handle, _query, -1, &_stmt, NULL) != SQLITE_OK) \
{ \
dt_print(DT_DEBUG_ALWAYS, "TRY_PREPARE '%s' sql: '%s'", _message, sqlite3_errmsg(db->handle)); \
FINALIZE; \
sqlite3_exec(db->handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL); \
return version; \
} \
} while(0)
// redefine this where needed
#define FINALIZE
/* do the real migration steps, returns the version the db was converted to */
static int _upgrade_library_schema_step(dt_database_t *db, int version)
{
sqlite3_stmt *stmt;
int new_version = version;
if(version == CURRENT_DATABASE_VERSION_LIBRARY)
return version;
else if(version == 0)
{
// this can't happen, we started with 1, but it's a good example how this function works
// <do some magic to the db>
new_version = 1; // the version we transformed the db to. this way it might be possible to roll back or
// add fast paths
}
else if(version == 1)
{
// 1 -> 2 added write_timestamp
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("ALTER TABLE main.images ADD COLUMN write_timestamp INTEGER",
"can't add `write_timestamp' column to database");
TRY_EXEC("UPDATE main.images SET write_timestamp = STRFTIME('%s', 'now') WHERE write_timestamp IS NULL",
"can't initialize `write_timestamp' with current point in time");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 2;
}
else if(version == 2)
{
// 2 -> 3 reset raw_black and raw_maximum. in theory we should change the columns from REAL to INTEGER,
// but sqlite doesn't care about types so whatever
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("UPDATE main.images SET raw_black = 0, raw_maximum = 16384",
"can't reset raw_black and raw_maximum");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 3;
}
else if(version == 3)
{
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("CREATE TRIGGER insert_tag AFTER INSERT ON main.tags"
" BEGIN"
" INSERT INTO tagxtag SELECT id, new.id, 0 FROM TAGS;"
" UPDATE tagxtag SET count = 1000000 WHERE id1=new.id AND id2=new.id;"
" END",
"can't create insert_tag trigger");
TRY_EXEC("CREATE TRIGGER delete_tag BEFORE DELETE ON main.tags"
" BEGIN"
" DELETE FROM tagxtag WHERE id1=old.id OR id2=old.id;"
" DELETE FROM tagged_images WHERE tagid=old.id;"
" END",
"can't create delete_tag trigger");
TRY_EXEC("CREATE TRIGGER attach_tag AFTER INSERT ON main.tagged_images"
" BEGIN"
" UPDATE tagxtag"
" SET count = count + 1"
" WHERE (id1=new.tagid AND id2 IN (SELECT tagid FROM tagged_images WHERE imgid=new.imgid))"
" OR (id2=new.tagid AND id1 IN (SELECT tagid FROM tagged_images WHERE imgid=new.imgid));"
" END",
"can't create attach_tag trigger");
TRY_EXEC("CREATE TRIGGER detach_tag BEFORE DELETE ON main.tagged_images"
" BEGIN"
" UPDATE tagxtag"
" SET count = count - 1"
" WHERE (id1=old.tagid AND id2 IN (SELECT tagid FROM tagged_images WHERE imgid=old.imgid))"
" OR (id2=old.tagid AND id1 IN (SELECT tagid FROM tagged_images WHERE imgid=old.imgid));"
" END",
"can't create detach_tag trigger");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 4;
}
else if(version == 4)
{
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("ALTER TABLE main.presets RENAME TO tmp_presets", "can't rename table presets");
TRY_EXEC("CREATE TABLE main.presets (name VARCHAR, description VARCHAR, operation VARCHAR, op_params BLOB,"
"enabled INTEGER, blendop_params BLOB, model VARCHAR, maker VARCHAR, lens VARCHAR,"
"iso_min REAL, iso_max REAL, exposure_min REAL, exposure_max REAL, aperture_min REAL,"
"aperture_max REAL, focal_length_min REAL, focal_length_max REAL, writeprotect INTEGER,"
"autoapply INTEGER, filter INTEGER, def INTEGER, format INTEGER, op_version INTEGER,"
"blendop_version INTEGER, multi_priority INTEGER, multi_name VARCHAR(256))",
"can't create new presets table");
TRY_EXEC("INSERT INTO main.presets (name, description, operation, op_params, enabled, blendop_params, model, "
"maker, lens, iso_min, iso_max, exposure_min, exposure_max, aperture_min, aperture_max,"
"focal_length_min, focal_length_max, writeprotect, autoapply, filter, def, format, op_version, "
"blendop_version, multi_priority, multi_name) SELECT name, description, operation, op_params, "
"enabled, blendop_params, model, maker, lens, iso_min, iso_max, exposure_min, exposure_max, "
"aperture_min, aperture_max, focal_length_min, focal_length_max, writeprotect, autoapply, filter, "
"def, isldr, op_version, blendop_version, multi_priority, multi_name FROM tmp_presets",
"can't populate presets table from tmp_presets");
TRY_EXEC("DROP TABLE tmp_presets", "can't delete table tmp_presets");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 5;
}
else if(version == 5)
{
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("CREATE INDEX main.images_filename_index ON images (filename)",
"can't create index on image filename");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 6;
}
else if(version == 6)
{
// some ancient tables can have the styleid column of style_items be called style_id. fix that.
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
if(sqlite3_exec(db->handle, "SELECT style_id FROM main.style_items", NULL, NULL, NULL) == SQLITE_OK)
{
// clang-format off
TRY_EXEC("ALTER TABLE main.style_items RENAME TO tmp_style_items",
"can't rename table style_items");
TRY_EXEC("CREATE TABLE main.style_items (styleid INTEGER, num INTEGER, module INTEGER, "
"operation VARCHAR(256), op_params BLOB, enabled INTEGER, "
"blendop_params BLOB, blendop_version INTEGER, multi_priority INTEGER, multi_name VARCHAR(256))",
"can't create new style_items table");
TRY_EXEC("INSERT INTO main.style_items (styleid, num, module, operation, op_params, enabled,"
" blendop_params, blendop_version, multi_priority, multi_name)"
" SELECT style_id, num, module, operation, op_params, enabled,"
" blendop_params, blendop_version, multi_priority, multi_name"
" FROM tmp_style_items",
"can't populate style_items table from tmp_style_items");
TRY_EXEC("DROP TABLE tmp_style_items", "can't delete table tmp_style_items");
// clang-format on
}
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 7;
}
else if(version == 7)
{
// make sure that we have no film rolls with a NULL folder
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("ALTER TABLE main.film_rolls RENAME TO tmp_film_rolls", "can't rename table film_rolls");
TRY_EXEC("CREATE TABLE main.film_rolls "
"(id INTEGER PRIMARY KEY, datetime_accessed CHAR(20), "
"folder VARCHAR(1024) NOT NULL)",
"can't create new film_rolls table");
TRY_EXEC("INSERT INTO main.film_rolls (id, datetime_accessed, folder) "
"SELECT id, datetime_accessed, folder "
"FROM tmp_film_rolls "
"WHERE folder IS NOT NULL",
"can't populate film_rolls table from tmp_film_rolls");
TRY_EXEC("DROP TABLE tmp_film_rolls", "can't delete table tmp_film_rolls");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 8;
}
else if(version == 8)
{
// 8 -> 9 added history_end column to images
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("ALTER TABLE main.images ADD COLUMN history_end INTEGER",
"can't add `history_end' column to database");
TRY_EXEC("UPDATE main.images SET history_end = (SELECT IFNULL(MAX(num) + 1, 0) FROM main.history "
"WHERE imgid = id)",
"can't initialize `history_end' with last history entry");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 9;
}
else if(version == 9)
{
// 9 -> 10 cleanup of last update :(
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("UPDATE main.images SET history_end = (SELECT IFNULL(MAX(num) + 1, 0) FROM main.history "
"WHERE imgid = id)",
"can't set `history_end' to 0 where it was NULL");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 10;
}
else if(version == 10)
{
// 10 -> 11 added altitude column to images
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("ALTER TABLE main.images ADD COLUMN altitude REAL",
"can't add `altitude' column to database");
TRY_EXEC("UPDATE main.images SET altitude = NULL", "can't initialize `altitude' with NULL");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 11;
}
else if(version == 11)
{
// 11 -> 12 tagxtag was removed in order to reduce database size
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("DROP TRIGGER main.detach_tag", "can't drop trigger `detach_tag' from database");
TRY_EXEC("DROP TRIGGER main.attach_tag", "can't drop trigger `attach_tag' from database");
TRY_EXEC("DROP TRIGGER main.delete_tag", "can't drop trigger `delete_tag' from database");
TRY_EXEC("DROP TRIGGER main.insert_tag", "can't drop trigger `insert_tag' from database");
TRY_EXEC("DROP TABLE main.tagxtag", "can't drop table `tagxtag' from database");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 12;
}
else if(version == 12)
{
// 11 -> 12 move presets, styles and tags over to the data database
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
////////////// presets
#undef FINALIZE
#define FINALIZE \
do \
{ \
sqlite3_finalize(stmt); \
sqlite3_finalize(select_stmt); \
sqlite3_finalize(count_clashes_stmt); \
sqlite3_finalize(update_name_stmt); \
sqlite3_finalize(insert_stmt); \
sqlite3_finalize(delete_stmt); \
} while(0)
stmt = NULL;
sqlite3_stmt *insert_stmt = NULL, *delete_stmt = NULL, *select_stmt = NULL, *count_clashes_stmt = NULL,
*update_name_stmt = NULL;
// clang-format off
// remove presets that are already in data.
// we can't use a NATURAL JOIN here as that fails when columns have NULL values. :-(
TRY_EXEC("DELETE FROM main.presets WHERE rowid IN (SELECT p1.rowid FROM main.presets p1 "
"JOIN data.presets p2 ON "
"p1.name IS p2.name AND "
"p1.description IS p2.description AND "
"p1.operation IS p2.operation AND "
"p1.op_version IS p2.op_version AND "
"p1.op_params IS p2.op_params AND "
"p1.enabled IS p2.enabled AND "
"p1.blendop_params IS p2.blendop_params AND "
"p1.blendop_version IS p2.blendop_version AND "
"p1.multi_priority IS p2.multi_priority AND "
"p1.multi_name IS p2.multi_name AND "
"p1.model IS p2.model AND "
"p1.maker IS p2.maker AND "
"p1.lens IS p2.lens AND "
"p1.iso_min IS p2.iso_min AND "
"p1.iso_max IS p2.iso_max AND "
"p1.exposure_min IS p2.exposure_min AND "
"p1.exposure_max IS p2.exposure_max AND "
"p1.aperture_min IS p2.aperture_min AND "
"p1.aperture_max IS p2.aperture_max AND "
"p1.focal_length_min IS p2.focal_length_min AND "
"p1.focal_length_max IS p2.focal_length_max AND "
"p1.writeprotect IS p2.writeprotect AND "
"p1.autoapply IS p2.autoapply AND "
"p1.filter IS p2.filter AND "
"p1.def IS p2.def AND "
"p1.format IS p2.format "
"WHERE p1.writeprotect = 0)",
"can't delete already migrated presets from database");
// find all presets that are clashing with something else in presets. that can happen as we introduced an
// index on presets in data which wasn't in place in library.
TRY_PREPARE(select_stmt, "SELECT p.rowid, r FROM main.presets AS p, (SELECT rowid AS r, name, operation, "
"op_version FROM main.presets GROUP BY name, operation, op_version HAVING "
"COUNT(*) > 1) USING (name, operation, op_version) WHERE p.rowid != r",
"can't prepare selecting presets with same name, operation, op_version from database");
// see if an updated preset name still causes problems
TRY_PREPARE(count_clashes_stmt, "SELECT COUNT(*) FROM main.presets AS p, (SELECT name, operation, op_version "
"FROM main.presets WHERE rowid = ?1) AS i ON p.name = i.name || \" #\" || ?2 "
"AND p.operation = i.operation AND p.op_version = i.op_version",
"can't prepare selection of preset count by name from database");
// update the preset name for good
TRY_PREPARE(update_name_stmt, "UPDATE main.presets SET name = name || \" #\" || ?1 WHERE rowid = ?2",
"can't prepare updating of preset name in database");
// find all presets that would be clashing with something in data
TRY_PREPARE(stmt, "SELECT p1.rowid FROM main.presets p1 INNER JOIN data.presets p2 "
"USING (name, operation, op_version) WHERE p1.writeprotect = 0",
"can't access table `presets' in database");
// ... and move them over with a new name
TRY_PREPARE(insert_stmt, "INSERT OR FAIL INTO data.presets (name, description, operation, op_version, "
"op_params, enabled, blendop_params, blendop_version, multi_priority, multi_name, "
"model, maker, lens, iso_min, iso_max, exposure_min, exposure_max, aperture_min, "
"aperture_max, focal_length_min, focal_length_max, writeprotect, autoapply, filter, "
"def, format) "
"SELECT name || \" #\" || ?1, description, operation, op_version, op_params, "
"enabled, blendop_params, blendop_version, multi_priority, multi_name, model, maker, "
"lens, iso_min, iso_max, exposure_min, exposure_max, aperture_min, aperture_max, "
"focal_length_min, focal_length_max, writeprotect, autoapply, filter, def, format "
"FROM main.presets p1 WHERE p1.rowid = ?2",
"can't prepare insertion statement");
TRY_PREPARE(delete_stmt, "DELETE FROM main.presets WHERE rowid = ?1", "can't prepare deletion statement");
// clang-format on
// first rename presets with (name, operation, op_version) not being unique
while(sqlite3_step(select_stmt) == SQLITE_ROW)
{
const int own_rowid = sqlite3_column_int(select_stmt, 0);
const int other_rowid = sqlite3_column_int(select_stmt, 1);
int preset_version = 0;
do
{
preset_version++;
sqlite3_reset(count_clashes_stmt);
sqlite3_clear_bindings(count_clashes_stmt);
sqlite3_bind_int(count_clashes_stmt, 1, other_rowid);
sqlite3_bind_int(count_clashes_stmt, 2, preset_version);
}
while(sqlite3_step(count_clashes_stmt) == SQLITE_ROW && sqlite3_column_int(count_clashes_stmt, 0) > 0);
sqlite3_bind_int(update_name_stmt, 1, preset_version);
sqlite3_bind_int(update_name_stmt, 2, own_rowid);
TRY_STEP(update_name_stmt, SQLITE_DONE, "can't rename preset in database");
sqlite3_reset(update_name_stmt);
sqlite3_reset(update_name_stmt);
}
// now rename to avoid clashes with data.presets
while(sqlite3_step(stmt) == SQLITE_ROW)
{
int preset_version = 0;
const int rowid = sqlite3_column_int(stmt, 0);
do
{
preset_version++;
sqlite3_reset(insert_stmt);
sqlite3_clear_bindings(insert_stmt);
sqlite3_bind_int(insert_stmt, 1, preset_version);
sqlite3_bind_int(insert_stmt, 2, rowid);
} while(sqlite3_step(insert_stmt) != SQLITE_DONE);
sqlite3_reset(delete_stmt);
sqlite3_clear_bindings(delete_stmt);
sqlite3_bind_int(delete_stmt, 1, rowid);
TRY_STEP(delete_stmt, SQLITE_DONE, "can't delete preset from database");
}
// clang-format off
// all that is left in presets should be those that can be moved over without any further concerns
TRY_EXEC("INSERT OR FAIL INTO data.presets SELECT name, description, operation, "
"op_version, op_params, enabled, blendop_params, blendop_version, "
"multi_priority, multi_name, model, maker, lens, iso_min, iso_max, "
"exposure_min, exposure_max, aperture_min, aperture_max, "
"focal_length_min, focal_length_max, writeprotect, autoapply, filter, "
"def, format FROM main.presets WHERE writeprotect = 0",
"can't copy presets to the data database");
// ... delete them on the old side
TRY_EXEC("DELETE FROM main.presets WHERE writeprotect = 0",
"can't copy presets to the data database");
// clang-format on
FINALIZE;
#undef FINALIZE
////////////// styles
#define FINALIZE \
do \
{ \
sqlite3_finalize(stmt); \
sqlite3_finalize(insert_stmt); \
sqlite3_finalize(select_stmt); \
sqlite3_finalize(delete_stmt); \
sqlite3_finalize(update_name_stmt); \
sqlite3_finalize(select_new_stmt); \
sqlite3_finalize(copy_style_items_stmt); \
sqlite3_finalize(delete_style_items_stmt); \
} while(0)
stmt = NULL;
select_stmt = NULL;
update_name_stmt = NULL;
insert_stmt = NULL;
delete_stmt = NULL;
sqlite3_stmt *select_new_stmt = NULL, *copy_style_items_stmt = NULL, *delete_style_items_stmt = NULL;
// clang-format off
TRY_PREPARE(stmt, "SELECT id, name FROM main.styles", "can't prepare style selection from database");
TRY_PREPARE(select_stmt, "SELECT rowid FROM data.styles WHERE name = ?1 LIMIT 1",
"can't prepare style item selection from database");
TRY_PREPARE(update_name_stmt, "UPDATE main.styles SET name = ?1 WHERE id = ?2",
"can't prepare style name update");
TRY_PREPARE(insert_stmt, "INSERT INTO data.styles (id, name, description) "
"SELECT (SELECT COALESCE(MAX(id),0)+1 FROM data.styles), name, description "
"FROM main.styles where id = ?1",
"can't prepare style insertion for database");
TRY_PREPARE(delete_stmt, "DELETE FROM main.styles WHERE id = ?1",
"can't prepare style deletion for database");
TRY_PREPARE(select_new_stmt, "SELECT id FROM data.styles WHERE rowid = ?1",
"can't prepare style selection from data database");
TRY_PREPARE(copy_style_items_stmt, "INSERT INTO data.style_items "
"(styleid, num, module, operation, op_params, enabled, blendop_params, "
"blendop_version, multi_priority, multi_name) "
"SELECT ?1, num, module, operation, op_params, enabled, blendop_params, "
"blendop_version, multi_priority, multi_name FROM main.style_items "
"WHERE styleid = ?2",
"can't prepare style item copy into data database");
TRY_PREPARE(delete_style_items_stmt, "DELETE FROM main.style_items WHERE styleid = ?1",
"can't prepare style item deletion for database");
// clang-format on
while(sqlite3_step(stmt) == SQLITE_ROW)
{
const int id = sqlite3_column_int(stmt, 0);
const char *name = (const char *)sqlite3_column_text(stmt, 1);
// find a unique name of the style for data.styles
sqlite3_bind_text(select_stmt, 1, name, -1, SQLITE_TRANSIENT);
if(sqlite3_step(select_stmt) == SQLITE_ROW)
{
// we need to append a version
int style_version = 0;
char *new_name = NULL;
do
{
style_version++;
g_free(new_name);
new_name = g_strdup_printf("%s #%d", name, style_version);
sqlite3_reset(select_stmt);
sqlite3_clear_bindings(select_stmt);
sqlite3_bind_text(select_stmt, 1, new_name, -1, SQLITE_TRANSIENT);
} while(sqlite3_step(select_stmt) == SQLITE_ROW);
// update the name in the old place
sqlite3_bind_text(update_name_stmt, 1, new_name, -1, SQLITE_TRANSIENT);
sqlite3_bind_int(update_name_stmt, 2, id);
TRY_STEP(update_name_stmt, SQLITE_DONE, "can't update name of style in database");
sqlite3_reset(update_name_stmt);
sqlite3_clear_bindings(update_name_stmt);
g_free(new_name);
}
// move the style to data.styles and get the rowid
sqlite3_bind_int(insert_stmt, 1, id);
TRY_STEP(insert_stmt, SQLITE_DONE, "can't insert style into data database");
sqlite3_int64 last_rowid = sqlite3_last_insert_rowid(db->handle);
// delete style from styles
sqlite3_bind_int(delete_stmt, 1, id);
TRY_STEP(delete_stmt, SQLITE_DONE, "can't delete style from database");
sqlite3_bind_int(select_new_stmt, 1, last_rowid);
TRY_STEP(select_new_stmt, SQLITE_ROW, "can't select new style from data database");
const int new_id = sqlite3_column_int(select_new_stmt, 0);
// now that we have the style over in data.styles and the new id we can just copy over all style items
sqlite3_bind_int(copy_style_items_stmt, 1, new_id);
sqlite3_bind_int(copy_style_items_stmt, 2, id);
TRY_STEP(copy_style_items_stmt, SQLITE_DONE, "can't copy style items into data database");
// delete the style items from the old table
sqlite3_bind_int(delete_style_items_stmt, 1, id);
TRY_STEP(delete_style_items_stmt, SQLITE_DONE, "can't delete style items from database");
// cleanup for the next round
sqlite3_reset(insert_stmt);
sqlite3_clear_bindings(insert_stmt);
sqlite3_reset(select_stmt);
sqlite3_clear_bindings(select_stmt);
sqlite3_reset(delete_stmt);
sqlite3_clear_bindings(delete_stmt);
sqlite3_reset(select_new_stmt);
sqlite3_clear_bindings(select_new_stmt);
sqlite3_reset(copy_style_items_stmt);
sqlite3_clear_bindings(copy_style_items_stmt);
sqlite3_reset(delete_style_items_stmt);
sqlite3_clear_bindings(delete_style_items_stmt);
}
FINALIZE;
#undef FINALIZE
////////////// tags
#define FINALIZE
// clang-format off
// tags
TRY_EXEC("INSERT OR IGNORE INTO data.tags (name, icon, description, flags) "
"SELECT name, icon, description, flags FROM main.tags",
"can't prepare insertion of used tags into data database");
// tagged images
// we need a temp table to update tagged_images due to its primary key
TRY_EXEC("CREATE TEMPORARY TABLE tagged_images_tmp (imgid INTEGER, tagid INTEGER)",
"can't create temporary table for updating `tagged_images'");
TRY_EXEC("INSERT INTO tagged_images_tmp (imgid, tagid) "
"SELECT imgid, (SELECT t2.id FROM main.tags t1, data.tags t2 USING (name) WHERE t1.id = tagid) "
"FROM main.tagged_images",
"can't insert into `tagged_images_tmp'");
TRY_EXEC("DELETE FROM main.tagged_images", "can't delete tagged images in database");
TRY_EXEC("INSERT OR IGNORE INTO main.tagged_images (imgid, tagid) SELECT imgid, tagid FROM tagged_images_tmp",
"can't copy updated values back to `tagged_images'");
TRY_EXEC("DROP TABLE tagged_images_tmp", "can't drop table `tagged_images_tmp' from database");
////////////// cleanup - drop the indexes and tags
TRY_EXEC("DROP INDEX IF EXISTS main.presets_idx", "can't drop index `presets_idx' from database");
TRY_EXEC("DROP TABLE main.presets", "can't drop table `presets' from database");
TRY_EXEC("DROP TABLE main.style_items", "can't drop table `style_items' from database");
TRY_EXEC("DROP TABLE main.styles", "can't drop table `styles' from database");
TRY_EXEC("DROP TABLE main.tags", "can't drop table `tags' from database");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 13;
} else if(version == 13)
{
// 12 -> 13 bring back the used tag names to library.db so people can use it independently of data.db
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("CREATE TABLE main.used_tags (id INTEGER, name VARCHAR NOT NULL)",
"can't create `used_tags` table");
TRY_EXEC("CREATE INDEX main.used_tags_idx ON used_tags (id, name)",
"can't create index on table `used_tags' in database");
TRY_EXEC("INSERT INTO main.used_tags (id, name) SELECT t.id, t.name FROM data.tags AS t, main.tagged_images "
"AS i ON t.id = i.tagid GROUP BY t.id",
"can't insert used tags into `used_tags` table in database");
// clang-format on
sqlite3_exec(db->handle, "COMMIT", NULL, NULL, NULL);
new_version = 14;
}
else if(version == 14)
{
// 13 -> fix the index on used_tags to be a UNIQUE index :-/
sqlite3_exec(db->handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
// clang-format off
TRY_EXEC("DELETE FROM main.used_tags WHERE rowid NOT IN (SELECT rowid FROM used_tags GROUP BY id)",
"can't delete duplicated entries from `used_tags' in database");