sqlite-hub 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,153 @@
1
+ const express = require("express");
2
+ const {
3
+ DatabaseRequiredError,
4
+ route,
5
+ successResponse,
6
+ } = require("../utils/errors");
7
+
8
+ function getActiveDatabaseKey(connectionManager) {
9
+ return connectionManager.getActiveConnection()?.id ?? null;
10
+ }
11
+
12
+ function requireActiveDatabaseKey(connectionManager) {
13
+ const databaseKey = getActiveDatabaseKey(connectionManager);
14
+
15
+ if (!databaseKey) {
16
+ throw new DatabaseRequiredError();
17
+ }
18
+
19
+ return databaseKey;
20
+ }
21
+
22
+ function createChartsRouter({ appStateStore, connectionManager, sqlExecutor }) {
23
+ const router = express.Router();
24
+
25
+ router.get(
26
+ "/query-history",
27
+ route((req, res) => {
28
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
29
+
30
+ res.json(
31
+ successResponse({
32
+ data: appStateStore.getChartQueryHistoryList(databaseKey),
33
+ metadata: { databaseKey },
34
+ })
35
+ );
36
+ })
37
+ );
38
+
39
+ router.get(
40
+ "/query-history/:historyId",
41
+ route((req, res) => {
42
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
43
+
44
+ res.json(
45
+ successResponse({
46
+ data: appStateStore.getQueryHistoryChartsDetail(req.params.historyId, databaseKey),
47
+ metadata: {
48
+ databaseKey,
49
+ historyId: Number(req.params.historyId),
50
+ },
51
+ })
52
+ );
53
+ })
54
+ );
55
+
56
+ router.post(
57
+ "/query-history/:historyId/execute",
58
+ route((req, res) => {
59
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
60
+ const item = appStateStore.getQueryHistoryItemForDatabase(req.params.historyId, databaseKey);
61
+ const result = sqlExecutor.execute(item.rawSql, {
62
+ persistHistory: false,
63
+ requireReader: true,
64
+ });
65
+
66
+ res.json(
67
+ successResponse({
68
+ message: "Query results loaded for charts.",
69
+ data: {
70
+ ...result,
71
+ queryHistoryId: item.id,
72
+ },
73
+ metadata: {
74
+ databaseKey,
75
+ historyId: item.id,
76
+ },
77
+ timingMs: result.timingMs,
78
+ })
79
+ );
80
+ })
81
+ );
82
+
83
+ router.post(
84
+ "/",
85
+ route((req, res) => {
86
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
87
+ const chart = appStateStore.createQueryHistoryChart({
88
+ databaseKey,
89
+ queryHistoryId: req.body?.queryHistoryId,
90
+ name: req.body?.name,
91
+ chartType: req.body?.chartType,
92
+ config: req.body?.config,
93
+ resultColumns: req.body?.resultColumns,
94
+ tableVisible: req.body?.tableVisible,
95
+ });
96
+
97
+ res.json(
98
+ successResponse({
99
+ message: "Chart created.",
100
+ data: chart,
101
+ metadata: { databaseKey },
102
+ })
103
+ );
104
+ })
105
+ );
106
+
107
+ router.patch(
108
+ "/:chartId",
109
+ route((req, res) => {
110
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
111
+ const chart = appStateStore.updateQueryHistoryChart(req.params.chartId, {
112
+ databaseKey,
113
+ name: req.body?.name,
114
+ chartType: req.body?.chartType,
115
+ config: req.body?.config,
116
+ resultColumns: req.body?.resultColumns,
117
+ tableVisible: req.body?.tableVisible,
118
+ });
119
+
120
+ res.json(
121
+ successResponse({
122
+ message: "Chart updated.",
123
+ data: chart,
124
+ metadata: { databaseKey },
125
+ })
126
+ );
127
+ })
128
+ );
129
+
130
+ router.delete(
131
+ "/:chartId",
132
+ route((req, res) => {
133
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
134
+ appStateStore.deleteQueryHistoryChart(req.params.chartId, databaseKey);
135
+
136
+ res.json(
137
+ successResponse({
138
+ message: "Chart deleted.",
139
+ data: {
140
+ id: Number(req.params.chartId),
141
+ },
142
+ metadata: { databaseKey },
143
+ })
144
+ );
145
+ })
146
+ );
147
+
148
+ return router;
149
+ }
150
+
151
+ module.exports = {
152
+ createChartsRouter,
153
+ };
package/server/server.js CHANGED
@@ -15,6 +15,7 @@ const { TableDesignerService } = require("./services/sqlite/tableDesignerService
15
15
  const { createConnectionsRouter } = require("./routes/connections");
16
16
  const { createOverviewRouter } = require("./routes/overview");
17
17
  const { createSqlRouter } = require("./routes/sql");
18
+ const { createChartsRouter } = require("./routes/charts");
18
19
  const { createStructureRouter } = require("./routes/structure");
19
20
  const { createDataRouter } = require("./routes/data");
20
21
  const { createTableDesignerRouter } = require("./routes/tableDesigner");
@@ -79,6 +80,7 @@ app.use(
79
80
  );
80
81
  app.use("/api/db", createOverviewRouter({ overviewService }));
81
82
  app.use("/api/sql", createSqlRouter({ appStateStore, connectionManager, sqlExecutor }));
83
+ app.use("/api/charts", createChartsRouter({ appStateStore, connectionManager, sqlExecutor }));
82
84
  app.use("/api/structure", createStructureRouter({ structureService }));
83
85
  app.use("/api/data", createDataRouter({ dataBrowserService }));
84
86
  app.use("/api/table-designer", createTableDesignerRouter({ tableDesignerService }));
@@ -109,6 +111,10 @@ app.use(
109
111
  "/vendor/elkjs",
110
112
  express.static(path.resolve(__dirname, "..", "node_modules", "elkjs"))
111
113
  );
114
+ app.use(
115
+ "/vendor/echarts",
116
+ express.static(path.resolve(__dirname, "..", "node_modules", "echarts"))
117
+ );
112
118
  app.use(express.static(FRONTEND_ROOT));
113
119
  app.use("/db_logos", express.static(path.join(APP_STATE_DIRECTORY, "db_logos")));
114
120
  app.use(errorMiddleware);
@@ -1,7 +1,7 @@
1
1
  const fs = require("node:fs");
2
2
  const path = require("node:path");
3
3
  const Database = require("better-sqlite3");
4
- const { NotFoundError, ValidationError } = require("../../utils/errors");
4
+ const { ConflictError, NotFoundError, ValidationError } = require("../../utils/errors");
5
5
  const {
6
6
  buildAutoTitle,
7
7
  buildSqlPreview,
@@ -10,6 +10,13 @@ const {
10
10
  isDestructiveQuery,
11
11
  normalizeSql,
12
12
  } = require("./queryHistoryUtils");
13
+ const {
14
+ buildDefaultChartName,
15
+ normalizeChartConfig,
16
+ normalizeChartName,
17
+ normalizeChartType,
18
+ normalizeResultColumns,
19
+ } = require("./queryHistoryChartUtils");
13
20
 
14
21
  const DEFAULT_STATE = {
15
22
  recentConnections: [],
@@ -144,6 +151,20 @@ class AppStateStore {
144
151
  FOREIGN KEY (history_id) REFERENCES query_history(id) ON DELETE CASCADE
145
152
  );
146
153
 
154
+ CREATE TABLE IF NOT EXISTS query_history_chart (
155
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
156
+ query_history_id INTEGER NOT NULL,
157
+ name TEXT NOT NULL,
158
+ chart_type TEXT NOT NULL CHECK (chart_type IN ('bar', 'line', 'pie', 'scatter')),
159
+ config_json TEXT NOT NULL,
160
+ result_columns_json TEXT NOT NULL DEFAULT '[]',
161
+ table_visible INTEGER NOT NULL DEFAULT 1,
162
+ created_at TEXT NOT NULL,
163
+ updated_at TEXT NOT NULL,
164
+ FOREIGN KEY (query_history_id) REFERENCES query_history(id) ON DELETE CASCADE,
165
+ UNIQUE(query_history_id, name)
166
+ );
167
+
147
168
  CREATE INDEX IF NOT EXISTS idx_query_history_database_key
148
169
  ON query_history(database_key);
149
170
 
@@ -170,6 +191,15 @@ class AppStateStore {
170
191
 
171
192
  CREATE INDEX IF NOT EXISTS idx_query_runs_status
172
193
  ON query_runs(status);
194
+
195
+ CREATE INDEX IF NOT EXISTS idx_query_history_chart_query_history_id
196
+ ON query_history_chart(query_history_id);
197
+
198
+ CREATE INDEX IF NOT EXISTS idx_query_history_chart_query_history_updated_at
199
+ ON query_history_chart(query_history_id, updated_at DESC);
200
+
201
+ CREATE INDEX IF NOT EXISTS idx_query_history_chart_chart_type
202
+ ON query_history_chart(chart_type);
173
203
  `);
174
204
 
175
205
  const recentConnectionColumns = new Set(
@@ -528,6 +558,10 @@ class AppStateStore {
528
558
  const queryType = row.query_type ?? row.queryType ?? "other";
529
559
  const title = this.normalizeQueryHistoryText(row.title);
530
560
  const notes = this.normalizeQueryHistoryText(row.notes);
561
+ const chartTypes = String(row.chart_types ?? row.chartTypes ?? "")
562
+ .split(",")
563
+ .map((entry) => String(entry ?? "").trim().toLowerCase())
564
+ .filter((entry, index, values) => entry && values.indexOf(entry) === index);
531
565
  const lastRun =
532
566
  row.last_run_id ?? row.lastRunId
533
567
  ? this.decorateQueryRun({
@@ -557,6 +591,8 @@ class AppStateStore {
557
591
  useCount: Number(row.use_count ?? row.useCount ?? 0),
558
592
  firstExecutedAt: row.first_executed_at ?? row.firstExecutedAt ?? null,
559
593
  lastUsedAt: row.last_used_at ?? row.lastUsedAt ?? null,
594
+ chartCount: Number(row.chart_count ?? row.chartCount ?? 0),
595
+ chartTypes,
560
596
  displayTitle:
561
597
  title ||
562
598
  buildAutoTitle(row.raw_sql ?? row.rawSql, {
@@ -568,6 +604,58 @@ class AppStateStore {
568
604
  };
569
605
  }
570
606
 
607
+ parseQueryHistoryChartConfig(value) {
608
+ if (!value) {
609
+ return {};
610
+ }
611
+
612
+ if (typeof value === "object" && !Array.isArray(value)) {
613
+ return value;
614
+ }
615
+
616
+ try {
617
+ const parsed = JSON.parse(value);
618
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
619
+ } catch {
620
+ return {};
621
+ }
622
+ }
623
+
624
+ parseQueryHistoryChartResultColumns(value) {
625
+ if (Array.isArray(value)) {
626
+ return normalizeResultColumns(value);
627
+ }
628
+
629
+ if (!value) {
630
+ return [];
631
+ }
632
+
633
+ try {
634
+ return normalizeResultColumns(JSON.parse(value));
635
+ } catch {
636
+ return [];
637
+ }
638
+ }
639
+
640
+ decorateQueryHistoryChartRow(row = {}) {
641
+ return {
642
+ id: Number(row.id),
643
+ queryHistoryId: Number(row.query_history_id ?? row.queryHistoryId),
644
+ name: String(row.name ?? ""),
645
+ chartType: normalizeChartType(row.chart_type ?? row.chartType ?? "bar"),
646
+ config: normalizeChartConfig(
647
+ row.chart_type ?? row.chartType ?? "bar",
648
+ this.parseQueryHistoryChartConfig(row.config_json ?? row.configJson)
649
+ ),
650
+ resultColumns: this.parseQueryHistoryChartResultColumns(
651
+ row.result_columns_json ?? row.resultColumnsJson
652
+ ),
653
+ tableVisible: Boolean(row.table_visible ?? row.tableVisible),
654
+ createdAt: row.created_at ?? row.createdAt ?? null,
655
+ updatedAt: row.updated_at ?? row.updatedAt ?? null,
656
+ };
657
+ }
658
+
571
659
  buildQueryHistoryFilters({
572
660
  databaseKey,
573
661
  search,
@@ -952,6 +1040,65 @@ class AppStateStore {
952
1040
  });
953
1041
  }
954
1042
 
1043
+ getChartQueryHistoryList(databaseKey) {
1044
+ const normalizedDatabaseKey = this.normalizeQueryHistoryText(databaseKey);
1045
+
1046
+ if (!normalizedDatabaseKey) {
1047
+ return [];
1048
+ }
1049
+
1050
+ return this.db
1051
+ .prepare(`
1052
+ SELECT
1053
+ q.id,
1054
+ q.database_key,
1055
+ q.normalized_sql,
1056
+ q.raw_sql,
1057
+ q.title,
1058
+ q.notes,
1059
+ q.query_type,
1060
+ q.tables_detected,
1061
+ q.is_favorite,
1062
+ q.is_saved,
1063
+ q.is_destructive,
1064
+ q.use_count,
1065
+ q.first_executed_at,
1066
+ q.last_used_at,
1067
+ charts.chart_count,
1068
+ charts.chart_types,
1069
+ latest.id AS last_run_id,
1070
+ latest.executed_at AS last_run_executed_at,
1071
+ latest.duration_ms AS last_run_duration_ms,
1072
+ latest.row_count AS last_run_row_count,
1073
+ latest.status AS last_run_status,
1074
+ latest.error_message AS last_run_error_message,
1075
+ latest.affected_rows AS last_run_affected_rows
1076
+ FROM query_history q
1077
+ LEFT JOIN (
1078
+ SELECT
1079
+ query_history_id,
1080
+ COUNT(*) AS chart_count,
1081
+ GROUP_CONCAT(DISTINCT chart_type) AS chart_types
1082
+ FROM query_history_chart
1083
+ GROUP BY query_history_id
1084
+ ) charts
1085
+ ON charts.query_history_id = q.id
1086
+ LEFT JOIN query_runs latest
1087
+ ON latest.id = (
1088
+ SELECT runs.id
1089
+ FROM query_runs runs
1090
+ WHERE runs.history_id = q.id
1091
+ ORDER BY runs.executed_at DESC, runs.id DESC
1092
+ LIMIT 1
1093
+ )
1094
+ WHERE q.database_key = ?
1095
+ AND COALESCE(latest.status, 'success') != 'error'
1096
+ ORDER BY q.last_used_at DESC, q.id DESC
1097
+ `)
1098
+ .all(normalizedDatabaseKey)
1099
+ .map((row) => this.decorateQueryHistoryRow(row));
1100
+ }
1101
+
955
1102
  getQueryHistoryItemById(historyId) {
956
1103
  const row = this.db
957
1104
  .prepare(`
@@ -997,6 +1144,17 @@ class AppStateStore {
997
1144
  return this.decorateQueryHistoryRow(row);
998
1145
  }
999
1146
 
1147
+ getQueryHistoryItemForDatabase(historyId, databaseKey) {
1148
+ const item = this.getQueryHistoryItemById(historyId);
1149
+ const normalizedDatabaseKey = this.normalizeQueryHistoryText(databaseKey);
1150
+
1151
+ if (normalizedDatabaseKey && item.databaseKey !== normalizedDatabaseKey) {
1152
+ throw new NotFoundError(`Query history item not found: ${historyId}`);
1153
+ }
1154
+
1155
+ return item;
1156
+ }
1157
+
1000
1158
  getQueryRunsByHistoryId(historyId, limit = 8) {
1001
1159
  const normalizedLimit = Math.max(1, Math.min(50, Number(limit) || 8));
1002
1160
 
@@ -1020,6 +1178,95 @@ class AppStateStore {
1020
1178
  .map((row) => this.decorateQueryRun(row));
1021
1179
  }
1022
1180
 
1181
+ getQueryHistoryChartsByHistoryId(historyId) {
1182
+ return this.db
1183
+ .prepare(`
1184
+ SELECT
1185
+ id,
1186
+ query_history_id,
1187
+ name,
1188
+ chart_type,
1189
+ config_json,
1190
+ result_columns_json,
1191
+ table_visible,
1192
+ created_at,
1193
+ updated_at
1194
+ FROM query_history_chart
1195
+ WHERE query_history_id = ?
1196
+ ORDER BY created_at ASC, id ASC
1197
+ `)
1198
+ .all(Number(historyId))
1199
+ .map((row) => this.decorateQueryHistoryChartRow(row));
1200
+ }
1201
+
1202
+ getQueryHistoryChartById(chartId) {
1203
+ const row = this.db
1204
+ .prepare(`
1205
+ SELECT
1206
+ c.id,
1207
+ c.query_history_id,
1208
+ c.name,
1209
+ c.chart_type,
1210
+ c.config_json,
1211
+ c.result_columns_json,
1212
+ c.table_visible,
1213
+ c.created_at,
1214
+ c.updated_at
1215
+ FROM query_history_chart c
1216
+ WHERE c.id = ?
1217
+ `)
1218
+ .get(Number(chartId));
1219
+
1220
+ if (!row) {
1221
+ throw new NotFoundError(`Query history chart not found: ${chartId}`);
1222
+ }
1223
+
1224
+ return this.decorateQueryHistoryChartRow(row);
1225
+ }
1226
+
1227
+ getQueryHistoryChartForDatabase(chartId, databaseKey) {
1228
+ const row = this.db
1229
+ .prepare(`
1230
+ SELECT
1231
+ c.id,
1232
+ c.query_history_id,
1233
+ c.name,
1234
+ c.chart_type,
1235
+ c.config_json,
1236
+ c.result_columns_json,
1237
+ c.table_visible,
1238
+ c.created_at,
1239
+ c.updated_at,
1240
+ q.database_key
1241
+ FROM query_history_chart c
1242
+ INNER JOIN query_history q
1243
+ ON q.id = c.query_history_id
1244
+ WHERE c.id = ?
1245
+ `)
1246
+ .get(Number(chartId));
1247
+
1248
+ if (!row) {
1249
+ throw new NotFoundError(`Query history chart not found: ${chartId}`);
1250
+ }
1251
+
1252
+ const normalizedDatabaseKey = this.normalizeQueryHistoryText(databaseKey);
1253
+
1254
+ if (normalizedDatabaseKey && row.database_key !== normalizedDatabaseKey) {
1255
+ throw new NotFoundError(`Query history chart not found: ${chartId}`);
1256
+ }
1257
+
1258
+ return this.decorateQueryHistoryChartRow(row);
1259
+ }
1260
+
1261
+ getQueryHistoryChartsDetail(historyId, databaseKey) {
1262
+ const item = this.getQueryHistoryItemForDatabase(historyId, databaseKey);
1263
+
1264
+ return {
1265
+ item,
1266
+ charts: this.getQueryHistoryChartsByHistoryId(item.id),
1267
+ };
1268
+ }
1269
+
1023
1270
  updateQueryHistoryField(historyId, fieldName, value) {
1024
1271
  const result = this.db
1025
1272
  .prepare(`UPDATE query_history SET ${fieldName} = ? WHERE id = ?`)
@@ -1032,6 +1279,158 @@ class AppStateStore {
1032
1279
  return this.getQueryHistoryItemById(historyId);
1033
1280
  }
1034
1281
 
1282
+ resolveUniqueQueryHistoryChartName(queryHistoryId, candidateName, { excludeChartId = null } = {}) {
1283
+ const baseName = normalizeChartName(candidateName) || "Chart";
1284
+ let nextName = baseName;
1285
+ let suffix = 2;
1286
+
1287
+ while (true) {
1288
+ const row = this.db
1289
+ .prepare(`
1290
+ SELECT id
1291
+ FROM query_history_chart
1292
+ WHERE query_history_id = ?
1293
+ AND name = ?
1294
+ ${excludeChartId ? "AND id != ?" : ""}
1295
+ LIMIT 1
1296
+ `)
1297
+ .get(
1298
+ Number(queryHistoryId),
1299
+ nextName,
1300
+ ...(excludeChartId ? [Number(excludeChartId)] : [])
1301
+ );
1302
+
1303
+ if (!row) {
1304
+ return nextName;
1305
+ }
1306
+
1307
+ nextName = `${baseName}_${suffix}`;
1308
+ suffix += 1;
1309
+ }
1310
+ }
1311
+
1312
+ createQueryHistoryChart({
1313
+ queryHistoryId,
1314
+ name,
1315
+ chartType,
1316
+ config,
1317
+ resultColumns = [],
1318
+ tableVisible = true,
1319
+ databaseKey = null,
1320
+ } = {}) {
1321
+ const item = this.getQueryHistoryItemForDatabase(queryHistoryId, databaseKey);
1322
+ const normalizedChartType = normalizeChartType(chartType);
1323
+ const normalizedConfig = normalizeChartConfig(normalizedChartType, config);
1324
+ const normalizedResultColumns = normalizeResultColumns(resultColumns);
1325
+ const requestedName =
1326
+ normalizeChartName(name) || buildDefaultChartName(normalizedChartType, item.displayTitle);
1327
+ const uniqueName = this.resolveUniqueQueryHistoryChartName(item.id, requestedName);
1328
+ const timestamp = new Date().toISOString();
1329
+ const insertResult = this.db
1330
+ .prepare(`
1331
+ INSERT INTO query_history_chart (
1332
+ query_history_id,
1333
+ name,
1334
+ chart_type,
1335
+ config_json,
1336
+ result_columns_json,
1337
+ table_visible,
1338
+ created_at,
1339
+ updated_at
1340
+ )
1341
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1342
+ `)
1343
+ .run(
1344
+ item.id,
1345
+ uniqueName,
1346
+ normalizedChartType,
1347
+ JSON.stringify(normalizedConfig),
1348
+ JSON.stringify(normalizedResultColumns),
1349
+ tableVisible ? 1 : 0,
1350
+ timestamp,
1351
+ timestamp
1352
+ );
1353
+
1354
+ return this.getQueryHistoryChartById(insertResult.lastInsertRowid);
1355
+ }
1356
+
1357
+ updateQueryHistoryChart(
1358
+ chartId,
1359
+ { name, chartType, config, resultColumns, tableVisible, databaseKey = null } = {}
1360
+ ) {
1361
+ const existing = this.getQueryHistoryChartForDatabase(chartId, databaseKey);
1362
+ const nextChartType = chartType ? normalizeChartType(chartType) : existing.chartType;
1363
+ const nextName =
1364
+ name === undefined ? existing.name : normalizeChartName(name);
1365
+
1366
+ if (!nextName) {
1367
+ throw new ValidationError("Chart name is required.");
1368
+ }
1369
+
1370
+ const conflicting = this.db
1371
+ .prepare(`
1372
+ SELECT id
1373
+ FROM query_history_chart
1374
+ WHERE query_history_id = ?
1375
+ AND name = ?
1376
+ AND id != ?
1377
+ LIMIT 1
1378
+ `)
1379
+ .get(existing.queryHistoryId, nextName, existing.id);
1380
+
1381
+ if (conflicting) {
1382
+ throw new ConflictError(`A chart named "${nextName}" already exists for this query.`);
1383
+ }
1384
+
1385
+ const nextConfig = normalizeChartConfig(
1386
+ nextChartType,
1387
+ config === undefined ? existing.config : config
1388
+ );
1389
+ const nextResultColumns =
1390
+ resultColumns === undefined ? existing.resultColumns : normalizeResultColumns(resultColumns);
1391
+ const nextTableVisible =
1392
+ tableVisible === undefined ? existing.tableVisible : Boolean(tableVisible);
1393
+ const timestamp = new Date().toISOString();
1394
+
1395
+ this.db
1396
+ .prepare(`
1397
+ UPDATE query_history_chart
1398
+ SET
1399
+ name = ?,
1400
+ chart_type = ?,
1401
+ config_json = ?,
1402
+ result_columns_json = ?,
1403
+ table_visible = ?,
1404
+ updated_at = ?
1405
+ WHERE id = ?
1406
+ `)
1407
+ .run(
1408
+ nextName,
1409
+ nextChartType,
1410
+ JSON.stringify(nextConfig),
1411
+ JSON.stringify(nextResultColumns),
1412
+ nextTableVisible ? 1 : 0,
1413
+ timestamp,
1414
+ existing.id
1415
+ );
1416
+
1417
+ return this.getQueryHistoryChartById(existing.id);
1418
+ }
1419
+
1420
+ deleteQueryHistoryChart(chartId, databaseKey = null) {
1421
+ this.getQueryHistoryChartForDatabase(chartId, databaseKey);
1422
+
1423
+ const result = this.db
1424
+ .prepare("DELETE FROM query_history_chart WHERE id = ?")
1425
+ .run(Number(chartId));
1426
+
1427
+ if (!result.changes) {
1428
+ throw new NotFoundError(`Query history chart not found: ${chartId}`);
1429
+ }
1430
+
1431
+ return true;
1432
+ }
1433
+
1035
1434
  toggleFavorite(historyId, nextValue) {
1036
1435
  return this.updateQueryHistoryField(historyId, "is_favorite", nextValue ? 1 : 0);
1037
1436
  }