master
c 597 lines 20.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "sqlite_db_migration.h"
4
5 static int return_int_cb(void *data, int argc, char **argv, char **column)
6 {
7 int *status = data;
8 UNUSED(argc);
9 UNUSED(column);
10 *status = (int) str2uint32_t(argv[0], NULL);
11 return 0;
12 }
13
14 static int get_auto_vaccum(sqlite3 *database)
15 {
16 char *err_msg = NULL;
17 char sql[128];
18
19 int exists = 0;
20
21 snprintf(sql, sizeof(sql) - 1, "PRAGMA auto_vacuum");
22
23 int rc = sqlite3_exec_monitored(database, sql, return_int_cb, (void *) &exists, &err_msg);
24 if (rc != SQLITE_OK) {
25 netdata_log_info("Error checking database auto vacuum setting; %s", err_msg);
26 sqlite3_free(err_msg);
27 }
28
29 return exists;
30 }
31
32 int db_table_count(sqlite3 *database)
33 {
34 char *err_msg = NULL;
35 char sql[128];
36
37 int count = 0;
38 snprintf(sql, sizeof(sql) - 1, "select count(1) from sqlite_schema where type = 'table'");
39 int rc = sqlite3_exec_monitored(database, sql, return_int_cb, (void *) &count, &err_msg);
40 if (rc != SQLITE_OK) {
41 netdata_log_info("Error checking database table count; %s", err_msg);
42 sqlite3_free(err_msg);
43 }
44 return count;
45 }
46
47 int table_exists_in_database(sqlite3 *database, const char *table)
48 {
49 char *err_msg = NULL;
50 char sql[128];
51
52 int exists = 0;
53
54 snprintf(sql, sizeof(sql) - 1, "select 1 from sqlite_schema where type = 'table' and name = '%s'", table);
55
56 int rc = sqlite3_exec_monitored(database, sql, return_int_cb, (void *) &exists, &err_msg);
57 if (rc != SQLITE_OK) {
58 netdata_log_info("Error checking table existence; %s", err_msg);
59 sqlite3_free(err_msg);
60 }
61
62 return exists;
63 }
64
65 static int column_exists_in_table(sqlite3 *database, const char *table, const char *column)
66 {
67 char *err_msg = NULL;
68 char sql[128];
69
70 int exists = 0;
71
72 snprintf(sql, sizeof(sql) - 1, "SELECT 1 FROM pragma_table_info('%s') where name = '%s'", table, column);
73
74 int rc = sqlite3_exec_monitored(database, sql, return_int_cb, (void *) &exists, &err_msg);
75 if (rc != SQLITE_OK) {
76 netdata_log_info("Error checking column existence; %s", err_msg);
77 sqlite3_free(err_msg);
78 }
79
80 return exists;
81 }
82
83 static int get_database_user_version(sqlite3 *database)
84 {
85 int user_version = 0;
86
87 int rc = sqlite3_exec_monitored(database, "PRAGMA user_version", return_int_cb, (void *)&user_version, NULL);
88 if (rc != SQLITE_OK)
89 netdata_log_error("Failed to get user version for database");
90
91 return user_version;
92 }
93
94 const char *database_migrate_v1_v2[] = {
95 "ALTER TABLE host ADD hops INTEGER NOT NULL DEFAULT 0",
96 NULL
97 };
98
99 const char *database_migrate_v2_v3[] = {
100 "ALTER TABLE host ADD memory_mode INT NOT NULL DEFAULT 0",
101 "ALTER TABLE host ADD abbrev_timezone TEXT NOT NULL DEFAULT ''",
102 "ALTER TABLE host ADD utc_offset INT NOT NULL DEFAULT 0",
103 "ALTER TABLE host ADD program_name TEXT NOT NULL DEFAULT 'unknown'",
104 "ALTER TABLE host ADD program_version TEXT NOT NULL DEFAULT 'unknown'",
105 "ALTER TABLE host ADD entries INT NOT NULL DEFAULT 0",
106 "ALTER TABLE host ADD health_enabled INT NOT NULL DEFAULT 0",
107 NULL
108 };
109
110 const char *database_migrate_v4_v5[] = {
111 "DROP TABLE IF EXISTS chart_active",
112 "DROP TABLE IF EXISTS dimension_active",
113 "DROP TABLE IF EXISTS chart_hash",
114 "DROP TABLE IF EXISTS chart_hash_map",
115 "DROP VIEW IF EXISTS v_chart_hash",
116 NULL
117 };
118
119 const char *database_migrate_v5_v6[] = {
120 "DROP TRIGGER IF EXISTS tr_dim_del",
121 "DROP TABLE IF EXISTS dimension_delete",
122 NULL
123 };
124
125 const char *database_migrate_v9_v10[] = {
126 "ALTER TABLE alert_hash ADD chart_labels TEXT",
127 NULL
128 };
129
130 const char *database_migrate_v10_v11[] = {
131 "ALTER TABLE health_log ADD chart_name TEXT",
132 NULL
133 };
134
135 const char *database_migrate_v11_v12[] = {
136 "ALTER TABLE health_log_detail ADD summary TEXT",
137 "ALTER TABLE alert_hash ADD summary TEXT",
138 NULL
139 };
140
141 const char *database_migrate_v12_v13_detail[] = {
142 "ALTER TABLE health_log_detail ADD summary TEXT",
143 NULL
144 };
145
146 const char *database_migrate_v12_v13_hash[] = {
147 "ALTER TABLE alert_hash ADD summary TEXT",
148 NULL
149 };
150
151 const char *database_migrate_v13_v14[] = {
152 "ALTER TABLE host ADD last_connected INT NOT NULL DEFAULT 0",
153 NULL
154 };
155
156 const char *database_migrate_v16_v17[] = {
157 "ALTER TABLE alert_hash ADD time_group_condition INT",
158 "ALTER TABLE alert_hash ADD time_group_value DOUBLE",
159 "ALTER TABLE alert_hash ADD dims_group INT",
160 "ALTER TABLE alert_hash ADD data_source INT",
161 NULL
162 };
163
164 // Note: Same as database_migrate_v16_v17. This is not wrong
165 // Do additional migration to handle agents that created wrong alert_hash table
166 const char *database_migrate_v17_v18[] = {
167 "ALTER TABLE alert_hash ADD time_group_condition INT",
168 "ALTER TABLE alert_hash ADD time_group_value DOUBLE",
169 "ALTER TABLE alert_hash ADD dims_group INT",
170 "ALTER TABLE alert_hash ADD data_source INT",
171 NULL
172 };
173
174
175 static int do_migration_v1_v2(sqlite3 *database)
176 {
177 if (table_exists_in_database(database, "host") && !column_exists_in_table(database, "host", "hops"))
178 return init_database_batch(database, &database_migrate_v1_v2[0], "meta_migrate");
179 return 0;
180 }
181
182 static int do_migration_v2_v3(sqlite3 *database)
183 {
184 if (table_exists_in_database(database, "host") && !column_exists_in_table(database, "host", "memory_mode"))
185 return init_database_batch(database, &database_migrate_v2_v3[0], "meta_migrate");
186 return 0;
187 }
188
189 static int do_migration_v3_v4(sqlite3 *database)
190 {
191 char sql[256];
192
193 int rc;
194 sqlite3_stmt *res = NULL;
195 snprintfz(sql, sizeof(sql) - 1, "SELECT name FROM sqlite_schema WHERE type ='table' AND name LIKE 'health_log_%%'");
196 rc = sqlite3_prepare_v2(database, sql, -1, &res, 0);
197 if (rc != SQLITE_OK) {
198 error_report("Failed to prepare statement to alter health_log tables");
199 return 1;
200 }
201
202 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
203 char *table = strdupz((char *) sqlite3_column_text(res, 0));
204 if (!column_exists_in_table(database, table, "chart_context")) {
205 snprintfz(sql, sizeof(sql) - 1, "ALTER TABLE %s ADD chart_context text", table);
206 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
207 }
208 freez(table);
209 }
210
211 SQLITE_FINALIZE(res);
212
213 return 0;
214 }
215
216 static int do_migration_v4_v5(sqlite3 *database)
217 {
218 return init_database_batch(database, &database_migrate_v4_v5[0], "meta_migrate");
219 }
220
221 static int do_migration_v5_v6(sqlite3 *database)
222 {
223 return init_database_batch(database, &database_migrate_v5_v6[0], "meta_migrate");
224 }
225
226 static int do_migration_v6_v7(sqlite3 *database)
227 {
228 char sql[256];
229
230 int rc;
231 sqlite3_stmt *res = NULL;
232 snprintfz(sql, sizeof(sql) - 1, "SELECT name FROM sqlite_schema WHERE type ='table' AND name LIKE 'aclk_alert_%%'");
233 rc = sqlite3_prepare_v2(database, sql, -1, &res, 0);
234 if (rc != SQLITE_OK) {
235 error_report("Failed to prepare statement to alter aclk_alert tables");
236 return 1;
237 }
238
239 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
240 char *table = strdupz((char *) sqlite3_column_text(res, 0));
241 if (!column_exists_in_table(database, table, "filtered_alert_unique_id")) {
242 snprintfz(sql, sizeof(sql) - 1, "ALTER TABLE %s ADD filtered_alert_unique_id", table);
243 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
244 snprintfz(sql, sizeof(sql) - 1, "UPDATE %s SET filtered_alert_unique_id = alert_unique_id", table);
245 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
246 }
247 freez(table);
248 }
249
250 SQLITE_FINALIZE(res);
251
252 return 0;
253 }
254
255 static int do_migration_v7_v8(sqlite3 *database)
256 {
257 char sql[256];
258
259 int rc;
260 sqlite3_stmt *res = NULL;
261 snprintfz(sql, sizeof(sql) - 1, "SELECT name FROM sqlite_schema WHERE type ='table' AND name LIKE 'health_log_%%'");
262 rc = sqlite3_prepare_v2(database, sql, -1, &res, 0);
263 if (rc != SQLITE_OK) {
264 error_report("Failed to prepare statement to alter health_log tables");
265 return 1;
266 }
267
268 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
269 char *table = strdupz((char *) sqlite3_column_text(res, 0));
270 if (!column_exists_in_table(database, table, "transition_id")) {
271 snprintfz(sql, sizeof(sql) - 1, "ALTER TABLE %s ADD transition_id blob", table);
272 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
273 }
274 freez(table);
275 }
276
277 SQLITE_FINALIZE(res);
278
279 return 0;
280 }
281
282 static int do_migration_v8_v9(sqlite3 *database)
283 {
284 char sql[2048];
285 int rc;
286 sqlite3_stmt *res = NULL;
287
288 //create the health_log table and it's index
289 snprintfz(sql, sizeof(sql) - 1, "CREATE TABLE IF NOT EXISTS health_log (health_log_id INTEGER PRIMARY KEY, host_id blob, alarm_id int, " \
290 "config_hash_id blob, name text, chart text, family text, recipient text, units text, exec text, " \
291 "chart_context text, last_transition_id blob, UNIQUE (host_id, alarm_id))");
292 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
293
294 //TODO indexes
295 snprintfz(sql, sizeof(sql) - 1, "CREATE INDEX IF NOT EXISTS health_log_ind_1 ON health_log (host_id)");
296 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
297
298 snprintfz(sql, sizeof(sql) - 1, "CREATE TABLE IF NOT EXISTS health_log_detail (health_log_id int, unique_id int, alarm_id int, alarm_event_id int, " \
299 "updated_by_id int, updates_id int, when_key int, duration int, non_clear_duration int, " \
300 "flags int, exec_run_timestamp int, delay_up_to_timestamp int, " \
301 "info text, exec_code int, new_status real, old_status real, delay int, " \
302 "new_value double, old_value double, last_repeat int, transition_id blob, global_id int, summary text, host_id blob)");
303 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
304
305 snprintfz(sql, sizeof(sql) - 1, "CREATE INDEX IF NOT EXISTS health_log_d_ind_1 ON health_log_detail (unique_id)");
306 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
307 snprintfz(sql, sizeof(sql) - 1, "CREATE INDEX IF NOT EXISTS health_log_d_ind_2 ON health_log_detail (global_id)");
308 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
309 snprintfz(sql, sizeof(sql) - 1, "CREATE INDEX IF NOT EXISTS health_log_d_ind_3 ON health_log_detail (transition_id)");
310 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
311 snprintfz(sql, sizeof(sql) - 1, "CREATE INDEX IF NOT EXISTS health_log_d_ind_4 ON health_log_detail (health_log_id)");
312 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
313
314 snprintfz(sql, sizeof(sql) - 1, "ALTER TABLE alert_hash ADD source text");
315 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
316
317 snprintfz(sql, sizeof(sql) - 1, "CREATE INDEX IF NOT EXISTS alert_hash_index ON alert_hash (hash_id)");
318 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
319
320 snprintfz(sql, sizeof(sql) - 1, "SELECT name FROM sqlite_schema WHERE type ='table' AND name LIKE 'health_log_%%' AND name <> 'health_log_detail'");
321 rc = sqlite3_prepare_v2(database, sql, -1, &res, 0);
322 if (rc != SQLITE_OK) {
323 error_report("Failed to prepare statement to alter health_log tables");
324 return 1;
325 }
326
327 DICTIONARY *dict_tables = dictionary_create(DICT_OPTION_NONE);
328
329 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
330 char *table = strdupz((char *) sqlite3_column_text(res, 0));
331 if (health_migrate_old_health_log_table(table)) {
332 dictionary_set(dict_tables, table, NULL, 0);
333 }
334 freez(table);
335 }
336
337 SQLITE_FINALIZE(res);
338
339 char *table = NULL;
340 dfe_start_read(dict_tables, table) {
341 sql_drop_table(table_dfe.name);
342 }
343 dfe_done(table);
344 dictionary_destroy(dict_tables);
345
346 snprintfz(sql, sizeof(sql) - 1, "ALTER TABLE health_log_detail DROP COLUMN host_id");
347 sqlite3_exec_monitored(database, sql, 0, 0, NULL);
348
349 return 0;
350 }
351
352 static int do_migration_v9_v10(sqlite3 *database)
353 {
354 if (table_exists_in_database(database, "alert_hash") && !column_exists_in_table(database, "alert_hash", "chart_labels"))
355 return init_database_batch(database, &database_migrate_v9_v10[0], "meta_migrate");
356 return 0;
357 }
358
359 static int do_migration_v10_v11(sqlite3 *database)
360 {
361 if (table_exists_in_database(database, "health_log") && !column_exists_in_table(database, "health_log", "chart_name"))
362 return init_database_batch(database, &database_migrate_v10_v11[0], "meta_migrate");
363
364 return 0;
365 }
366
367 #define MIGR_11_12_UPD_HEALTH_LOG_DETAIL "UPDATE health_log_detail SET summary = (select name from health_log where health_log_id = health_log_detail.health_log_id)"
368 static int do_migration_v11_v12(sqlite3 *database)
369 {
370 int rc = 0;
371
372 if (table_exists_in_database(database, "health_log_detail") && !column_exists_in_table(database, "health_log_detail", "summary") &&
373 table_exists_in_database(database, "alert_hash") && !column_exists_in_table(database, "alert_hash", "summary"))
374 rc = init_database_batch(database, &database_migrate_v11_v12[0], "meta_migrate");
375
376 if (!rc)
377 sqlite3_exec_monitored(database, MIGR_11_12_UPD_HEALTH_LOG_DETAIL, 0, 0, NULL);
378
379 return rc;
380 }
381
382 static int do_migration_v14_v15(sqlite3 *database)
383 {
384 char sql[256];
385
386 int rc;
387 sqlite3_stmt *res = NULL;
388 snprintfz(sql, sizeof(sql) - 1, "SELECT name FROM sqlite_schema WHERE type = \"index\" AND name LIKE \"aclk_alert_index@_%%\" ESCAPE \"@\"");
389 rc = sqlite3_prepare_v2(database, sql, -1, &res, 0);
390 if (rc != SQLITE_OK) {
391 error_report("Failed to prepare statement to drop unused indices");
392 return 1;
393 }
394
395 BUFFER *wb = buffer_create(128, NULL);
396 size_t count = 0;
397 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
398 buffer_sprintf(wb, "DROP INDEX IF EXISTS %s; ", (char *)sqlite3_column_text(res, 0));
399 count++;
400 }
401
402 SQLITE_FINALIZE(res);
403
404 if (count)
405 (void)db_execute(database, buffer_tostring(wb), NULL);
406
407 buffer_free(wb);
408 return 0;
409 }
410
411 static int do_migration_v15_v16(sqlite3 *database)
412 {
413 char sql[256];
414
415 int rc;
416 sqlite3_stmt *res = NULL;
417 snprintfz(sql, sizeof(sql) - 1, "SELECT name FROM sqlite_schema WHERE type = \"table\" AND name LIKE \"aclk_alert_%%\"");
418 rc = sqlite3_prepare_v2(database, sql, -1, &res, 0);
419 if (rc != SQLITE_OK) {
420 error_report("Failed to prepare statement to drop unused indices");
421 return 1;
422 }
423
424 BUFFER *wb = buffer_create(128, NULL);
425 size_t count = 0;
426 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
427 buffer_sprintf(wb, "ANALYZE %s ; ", (char *)sqlite3_column_text(res, 0));
428 count++;
429 }
430
431 SQLITE_FINALIZE(res);
432
433 if (count)
434 (void)db_execute(database, buffer_tostring(wb), NULL);
435
436 buffer_free(wb);
437 return 0;
438 }
439
440 static int do_migration_v16_v17(sqlite3 *database)
441 {
442 if (table_exists_in_database(database, "alert_hash") && !column_exists_in_table(database, "alert_hash", "time_group_condition"))
443 return init_database_batch(database, &database_migrate_v16_v17[0], "meta_migrate");
444
445 return 0;
446 }
447
448 static int do_migration_v17_v18(sqlite3 *database)
449 {
450 if (table_exists_in_database(database, "alert_hash") && !column_exists_in_table(database, "alert_hash", "time_group_condition"))
451 return init_database_batch(database, &database_migrate_v17_v18[0], "meta_migrate");
452
453 return 0;
454 }
455
456
457 static int do_migration_v12_v13(sqlite3 *database)
458 {
459 int rc = 0;
460
461 if (table_exists_in_database(database, "health_log_detail") && !column_exists_in_table(database, "health_log_detail", "summary")) {
462 rc = init_database_batch(database, &database_migrate_v12_v13_detail[0], "meta_migrate");
463 sqlite3_exec_monitored(database, MIGR_11_12_UPD_HEALTH_LOG_DETAIL, 0, 0, NULL);
464 }
465
466 if (table_exists_in_database(database, "alert_hash") && !column_exists_in_table(database, "alert_hash", "summary"))
467 rc = init_database_batch(database, &database_migrate_v12_v13_hash[0], "meta_migrate");
468
469 return rc;
470 }
471
472 static int do_migration_v13_v14(sqlite3 *database)
473 {
474 if (table_exists_in_database(database, "host") && !column_exists_in_table(database, "host", "last_connected"))
475 return init_database_batch(database, &database_migrate_v13_v14[0], "meta_migrate");
476
477 return 0;
478 }
479
480
481 // Actions for ML migration
482 const char *database_ml_migrate_v1_v2[] = {
483 "PRAGMA journal_mode=delete",
484 "PRAGMA journal_mode=WAL",
485 "PRAGMA auto_vacuum=2",
486 "VACUUM",
487 NULL
488 };
489
490 static int do_ml_migration_v1_v2(sqlite3 *database)
491 {
492 if (get_auto_vaccum(database) != 2)
493 return init_database_batch(database, &database_ml_migrate_v1_v2[0], "ml_migrate");
494 return 0;
495 }
496
497 static int do_migration_noop(sqlite3 *database)
498 {
499 UNUSED(database);
500 return 0;
501 }
502
503 typedef struct database_func_migration_list {
504 char *name;
505 int (*func)(sqlite3 *database);
506 } DATABASE_FUNC_MIGRATION_LIST;
507
508
509 static int migrate_database(sqlite3 *database, int target_version, char *db_name, DATABASE_FUNC_MIGRATION_LIST *migration_list)
510 {
511 int user_version = 0;
512 char *err_msg = NULL;
513
514 int rc = sqlite3_exec_monitored(database, "PRAGMA user_version", return_int_cb, (void *) &user_version, &err_msg);
515 if (rc != SQLITE_OK) {
516 netdata_log_info("Error checking the %s database version; %s", db_name, err_msg);
517 sqlite3_free(err_msg);
518 }
519
520 if (likely(user_version == target_version)) {
521 errno_clear();
522 netdata_log_info("%s database version is %d (no migration needed)", db_name, target_version);
523 return target_version;
524 }
525
526 netdata_log_info("Database version is %d, current version is %d. Running migration for %s ...", user_version, target_version, db_name);
527 for (int i = user_version; i < target_version && migration_list[i].func; i++) {
528 netdata_log_info("Running database \"%s\" migration %s", db_name, migration_list[i].name);
529 rc = (migration_list[i].func)(database);
530 if (unlikely(rc)) {
531 error_report("Database %s migration from version %d to version %d failed", db_name, i, i + 1);
532 return i;
533 }
534 }
535 return target_version;
536 }
537
538 DATABASE_FUNC_MIGRATION_LIST migration_action[] = {
539 {.name = "v0 to v1", .func = do_migration_noop},
540 {.name = "v1 to v2", .func = do_migration_v1_v2},
541 {.name = "v2 to v3", .func = do_migration_v2_v3},
542 {.name = "v3 to v4", .func = do_migration_v3_v4},
543 {.name = "v4 to v5", .func = do_migration_v4_v5},
544 {.name = "v5 to v6", .func = do_migration_v5_v6},
545 {.name = "v6 to v7", .func = do_migration_v6_v7},
546 {.name = "v7 to v8", .func = do_migration_v7_v8},
547 {.name = "v8 to v9", .func = do_migration_v8_v9},
548 {.name = "v9 to v10", .func = do_migration_v9_v10},
549 {.name = "v10 to v11", .func = do_migration_v10_v11},
550 {.name = "v11 to v12", .func = do_migration_v11_v12},
551 {.name = "v12 to v13", .func = do_migration_v12_v13},
552 {.name = "v13 to v14", .func = do_migration_v13_v14},
553 {.name = "v14 to v15", .func = do_migration_v14_v15},
554 {.name = "v15 to v16", .func = do_migration_v15_v16},
555 {.name = "v16 to v17", .func = do_migration_v16_v17},
556 {.name = "v17 to v18", .func = do_migration_v17_v18},
557 // the terminator of this array
558 {.name = NULL, .func = NULL}
559 };
560
561 DATABASE_FUNC_MIGRATION_LIST context_migration_action[] = {
562 {.name = "v0 to v1", .func = do_migration_noop},
563 // the terminator of this array
564 {.name = NULL, .func = NULL}
565 };
566
567 DATABASE_FUNC_MIGRATION_LIST ml_migration_action[] = {
568 {.name = "v0 to v1", .func = do_migration_noop},
569 {.name = "v1 to v2", .func = do_ml_migration_v1_v2},
570 // the terminator of this array
571 {.name = NULL, .func = NULL}
572 };
573
574 int perform_database_migration(sqlite3 *database, int target_version)
575 {
576 int user_version = get_database_user_version(database);
577
578 if (!user_version && !db_table_count(database))
579 return target_version;
580
581 return migrate_database(database, target_version, "metadata", migration_action);
582 }
583
584 int perform_context_database_migration(sqlite3 *database, int target_version)
585 {
586 int user_version = get_database_user_version(database);
587
588 if (!user_version && !table_exists_in_database(database, "context"))
589 return target_version;
590
591 return migrate_database(database, target_version, "context", context_migration_action);
592 }
593
594 int perform_ml_database_migration(sqlite3 *database, int target_version)
595 {
596 return migrate_database(database, target_version, "ml", ml_migration_action);
597 }