Prompt Workbench Core 1.0.0
A C library for prompt testing and evaluation with OpenAI-compatible APIs (Educational Use Only)
Loading...
Searching...
No Matches
db.c
Go to the documentation of this file.
1/*
2 * Prompt Workbench Core - Database Layer Implementation
3 * Copyright (c) 2025 nnlazaro
4 *
5 * This file is part of Prompt Workbench Core, licensed under the
6 * Educational and Non-Commercial Use License.
7 * See LICENSE file for details.
8 *
9 * FOR EDUCATIONAL USE ONLY - NOT FOR PRODUCTION OR COMMERCIAL USE
10 */
11
12#include "db.h"
13#include <sqlite3.h>
14#include <stdlib.h>
15#include <stdio.h>
16#include <string.h>
17
18static sqlite3* db = NULL;
19
20static const char* CREATE_TABLES_SQL =
21 "CREATE TABLE IF NOT EXISTS test_suites ("
22 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
23 " title TEXT NOT NULL,"
24 " description TEXT,"
25 " system_prompt TEXT NOT NULL,"
26 " model TEXT NOT NULL"
27 ");"
28 "CREATE TABLE IF NOT EXISTS user_prompts ("
29 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
30 " prompt TEXT NOT NULL,"
31 " test_suite_id INTEGER NOT NULL,"
32 " FOREIGN KEY (test_suite_id) REFERENCES test_suites(id) ON DELETE CASCADE"
33 ");";
34
35int db_init(const char* db_path) {
36 if (!db_path) {
37 return -1;
38 }
39
40 if (db) {
41 sqlite3_close(db);
42 db = NULL;
43 }
44
45 int rc = sqlite3_open(db_path, &db);
46 if (rc != SQLITE_OK) {
47 fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
48 sqlite3_close(db);
49 db = NULL;
50 return -1;
51 }
52
53 /* Enable foreign key constraints */
54 char* err_msg = NULL;
55 rc = sqlite3_exec(db, "PRAGMA foreign_keys = ON;", NULL, NULL, &err_msg);
56 if (rc != SQLITE_OK) {
57 fprintf(stderr, "Failed to enable foreign keys: %s\n", err_msg);
58 sqlite3_free(err_msg);
59 sqlite3_close(db);
60 db = NULL;
61 return -1;
62 }
63
64 rc = sqlite3_exec(db, CREATE_TABLES_SQL, NULL, NULL, &err_msg);
65 if (rc != SQLITE_OK) {
66 fprintf(stderr, "SQL error: %s\n", err_msg);
67 sqlite3_free(err_msg);
68 sqlite3_close(db);
69 db = NULL;
70 return -1;
71 }
72
73 return 0;
74}
75
76void db_close(void) {
77 if (db) {
78 sqlite3_close(db);
79 db = NULL;
80 }
81}
82
83/* Test Suite CRUD operations */
84
85int db_create_test_suite(const char* title, const char* description,
86 const char* system_prompt, const char* model) {
87 if (!db || !title || !system_prompt || !model) {
88 return -1;
89 }
90
91 const char* sql = "INSERT INTO test_suites (title, description, system_prompt, model) "
92 "VALUES (?, ?, ?, ?);";
93
94 sqlite3_stmt* stmt;
95 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
96 if (rc != SQLITE_OK) {
97 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
98 return -1;
99 }
100
101 sqlite3_bind_text(stmt, 1, title, -1, SQLITE_TRANSIENT);
102 sqlite3_bind_text(stmt, 2, description ? description : "", -1, SQLITE_TRANSIENT);
103 sqlite3_bind_text(stmt, 3, system_prompt, -1, SQLITE_TRANSIENT);
104 sqlite3_bind_text(stmt, 4, model, -1, SQLITE_TRANSIENT);
105
106 rc = sqlite3_step(stmt);
107 sqlite3_finalize(stmt);
108
109 if (rc != SQLITE_DONE) {
110 fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db));
111 return -1;
112 }
113
114 return (int)sqlite3_last_insert_rowid(db);
115}
116
118 if (!db || id <= 0) {
119 return NULL;
120 }
121
122 const char* sql = "SELECT id, title, description, system_prompt, model "
123 "FROM test_suites WHERE id = ?;";
124
125 sqlite3_stmt* stmt;
126 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
127 if (rc != SQLITE_OK) {
128 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
129 return NULL;
130 }
131
132 sqlite3_bind_int(stmt, 1, id);
133
134 rc = sqlite3_step(stmt);
135 if (rc != SQLITE_ROW) {
136 sqlite3_finalize(stmt);
137 return NULL;
138 }
139
140 TestSuite* suite = malloc(sizeof(TestSuite));
141 if (!suite) {
142 sqlite3_finalize(stmt);
143 return NULL;
144 }
145
146 suite->id = sqlite3_column_int(stmt, 0);
147 suite->title = strdup((const char*)sqlite3_column_text(stmt, 1));
148 suite->description = strdup((const char*)sqlite3_column_text(stmt, 2));
149 suite->system_prompt = strdup((const char*)sqlite3_column_text(stmt, 3));
150 suite->model = strdup((const char*)sqlite3_column_text(stmt, 4));
151
152 sqlite3_finalize(stmt);
153 return suite;
154}
155
157 if (!db || !count) {
158 return NULL;
159 }
160
161 *count = 0;
162
163 const char* sql = "SELECT id, title, description, system_prompt, model FROM test_suites;";
164
165 sqlite3_stmt* stmt;
166 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
167 if (rc != SQLITE_OK) {
168 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
169 return NULL;
170 }
171
172 int capacity = 10;
173 TestSuite** suites = malloc(sizeof(TestSuite*) * capacity);
174 if (!suites) {
175 sqlite3_finalize(stmt);
176 return NULL;
177 }
178
179 while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
180 if (*count >= capacity) {
181 capacity *= 2;
182 TestSuite** new_suites = realloc(suites, sizeof(TestSuite*) * capacity);
183 if (!new_suites) {
184 db_free_test_suites(suites, *count);
185 sqlite3_finalize(stmt);
186 return NULL;
187 }
188 suites = new_suites;
189 }
190
191 TestSuite* suite = malloc(sizeof(TestSuite));
192 if (!suite) {
193 db_free_test_suites(suites, *count);
194 sqlite3_finalize(stmt);
195 return NULL;
196 }
197
198 suite->id = sqlite3_column_int(stmt, 0);
199 suite->title = strdup((const char*)sqlite3_column_text(stmt, 1));
200 suite->description = strdup((const char*)sqlite3_column_text(stmt, 2));
201 suite->system_prompt = strdup((const char*)sqlite3_column_text(stmt, 3));
202 suite->model = strdup((const char*)sqlite3_column_text(stmt, 4));
203
204 suites[*count] = suite;
205 (*count)++;
206 }
207
208 sqlite3_finalize(stmt);
209
210 if (*count == 0) {
211 free(suites);
212 return NULL;
213 }
214
215 return suites;
216}
217
218int db_update_test_suite(int id, const char* title, const char* description,
219 const char* system_prompt, const char* model) {
220 if (!db || id <= 0) {
221 return -1;
222 }
223
224 if (!title && !description && !system_prompt && !model) {
225 return 0;
226 }
227
228 char sql[512] = "UPDATE test_suites SET ";
229 int first = 1;
230
231 if (title) {
232 strcat(sql, "title = ?");
233 first = 0;
234 }
235 if (description) {
236 if (!first) strcat(sql, ", ");
237 strcat(sql, "description = ?");
238 first = 0;
239 }
240 if (system_prompt) {
241 if (!first) strcat(sql, ", ");
242 strcat(sql, "system_prompt = ?");
243 first = 0;
244 }
245 if (model) {
246 if (!first) strcat(sql, ", ");
247 strcat(sql, "model = ?");
248 }
249
250 strcat(sql, " WHERE id = ?;");
251
252 sqlite3_stmt* stmt;
253 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
254 if (rc != SQLITE_OK) {
255 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
256 return -1;
257 }
258
259 int param = 1;
260 if (title) sqlite3_bind_text(stmt, param++, title, -1, SQLITE_TRANSIENT);
261 if (description) sqlite3_bind_text(stmt, param++, description, -1, SQLITE_TRANSIENT);
262 if (system_prompt) sqlite3_bind_text(stmt, param++, system_prompt, -1, SQLITE_TRANSIENT);
263 if (model) sqlite3_bind_text(stmt, param++, model, -1, SQLITE_TRANSIENT);
264 sqlite3_bind_int(stmt, param, id);
265
266 rc = sqlite3_step(stmt);
267 sqlite3_finalize(stmt);
268
269 if (rc != SQLITE_DONE) {
270 fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db));
271 return -1;
272 }
273
274 return 0;
275}
276
278 if (!db || id <= 0) {
279 return -1;
280 }
281
282 const char* sql = "DELETE FROM test_suites WHERE id = ?;";
283
284 sqlite3_stmt* stmt;
285 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
286 if (rc != SQLITE_OK) {
287 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
288 return -1;
289 }
290
291 sqlite3_bind_int(stmt, 1, id);
292
293 rc = sqlite3_step(stmt);
294 sqlite3_finalize(stmt);
295
296 if (rc != SQLITE_DONE) {
297 fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db));
298 return -1;
299 }
300
301 return 0;
302}
303
305 if (suite) {
306 free(suite->title);
307 free(suite->description);
308 free(suite->system_prompt);
309 free(suite->model);
310 free(suite);
311 }
312}
313
314void db_free_test_suites(TestSuite** suites, int count) {
315 if (suites) {
316 for (int i = 0; i < count; i++) {
317 db_free_test_suite(suites[i]);
318 }
319 free(suites);
320 }
321}
322
323/* User Prompt CRUD operations */
324
325int db_create_user_prompt(const char* prompt, int test_suite_id) {
326 if (!db || !prompt || test_suite_id <= 0) {
327 return -1;
328 }
329
330 const char* sql = "INSERT INTO user_prompts (prompt, test_suite_id) VALUES (?, ?);";
331
332 sqlite3_stmt* stmt;
333 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
334 if (rc != SQLITE_OK) {
335 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
336 return -1;
337 }
338
339 sqlite3_bind_text(stmt, 1, prompt, -1, SQLITE_TRANSIENT);
340 sqlite3_bind_int(stmt, 2, test_suite_id);
341
342 rc = sqlite3_step(stmt);
343 sqlite3_finalize(stmt);
344
345 if (rc != SQLITE_DONE) {
346 fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db));
347 return -1;
348 }
349
350 return (int)sqlite3_last_insert_rowid(db);
351}
352
354 if (!db || id <= 0) {
355 return NULL;
356 }
357
358 const char* sql = "SELECT id, prompt, test_suite_id FROM user_prompts WHERE id = ?;";
359
360 sqlite3_stmt* stmt;
361 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
362 if (rc != SQLITE_OK) {
363 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
364 return NULL;
365 }
366
367 sqlite3_bind_int(stmt, 1, id);
368
369 rc = sqlite3_step(stmt);
370 if (rc != SQLITE_ROW) {
371 sqlite3_finalize(stmt);
372 return NULL;
373 }
374
375 UserPrompt* prompt = malloc(sizeof(UserPrompt));
376 if (!prompt) {
377 sqlite3_finalize(stmt);
378 return NULL;
379 }
380
381 prompt->id = sqlite3_column_int(stmt, 0);
382 prompt->prompt = strdup((const char*)sqlite3_column_text(stmt, 1));
383 prompt->test_suite_id = sqlite3_column_int(stmt, 2);
384
385 sqlite3_finalize(stmt);
386 return prompt;
387}
388
389UserPrompt** db_get_user_prompts_by_suite(int test_suite_id, int* count) {
390 if (!db || test_suite_id <= 0 || !count) {
391 return NULL;
392 }
393
394 *count = 0;
395
396 const char* sql = "SELECT id, prompt, test_suite_id FROM user_prompts WHERE test_suite_id = ?;";
397
398 sqlite3_stmt* stmt;
399 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
400 if (rc != SQLITE_OK) {
401 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
402 return NULL;
403 }
404
405 sqlite3_bind_int(stmt, 1, test_suite_id);
406
407 int capacity = 10;
408 UserPrompt** prompts = malloc(sizeof(UserPrompt*) * capacity);
409 if (!prompts) {
410 sqlite3_finalize(stmt);
411 return NULL;
412 }
413
414 while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
415 if (*count >= capacity) {
416 capacity *= 2;
417 UserPrompt** new_prompts = realloc(prompts, sizeof(UserPrompt*) * capacity);
418 if (!new_prompts) {
419 db_free_user_prompts(prompts, *count);
420 sqlite3_finalize(stmt);
421 return NULL;
422 }
423 prompts = new_prompts;
424 }
425
426 UserPrompt* prompt = malloc(sizeof(UserPrompt));
427 if (!prompt) {
428 db_free_user_prompts(prompts, *count);
429 sqlite3_finalize(stmt);
430 return NULL;
431 }
432
433 prompt->id = sqlite3_column_int(stmt, 0);
434 prompt->prompt = strdup((const char*)sqlite3_column_text(stmt, 1));
435 prompt->test_suite_id = sqlite3_column_int(stmt, 2);
436
437 prompts[*count] = prompt;
438 (*count)++;
439 }
440
441 sqlite3_finalize(stmt);
442
443 if (*count == 0) {
444 free(prompts);
445 return NULL;
446 }
447
448 return prompts;
449}
450
452 if (!db || !count) {
453 return NULL;
454 }
455
456 *count = 0;
457
458 const char* sql = "SELECT id, prompt, test_suite_id FROM user_prompts;";
459
460 sqlite3_stmt* stmt;
461 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
462 if (rc != SQLITE_OK) {
463 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
464 return NULL;
465 }
466
467 int capacity = 10;
468 UserPrompt** prompts = malloc(sizeof(UserPrompt*) * capacity);
469 if (!prompts) {
470 sqlite3_finalize(stmt);
471 return NULL;
472 }
473
474 while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
475 if (*count >= capacity) {
476 capacity *= 2;
477 UserPrompt** new_prompts = realloc(prompts, sizeof(UserPrompt*) * capacity);
478 if (!new_prompts) {
479 db_free_user_prompts(prompts, *count);
480 sqlite3_finalize(stmt);
481 return NULL;
482 }
483 prompts = new_prompts;
484 }
485
486 UserPrompt* prompt = malloc(sizeof(UserPrompt));
487 if (!prompt) {
488 db_free_user_prompts(prompts, *count);
489 sqlite3_finalize(stmt);
490 return NULL;
491 }
492
493 prompt->id = sqlite3_column_int(stmt, 0);
494 prompt->prompt = strdup((const char*)sqlite3_column_text(stmt, 1));
495 prompt->test_suite_id = sqlite3_column_int(stmt, 2);
496
497 prompts[*count] = prompt;
498 (*count)++;
499 }
500
501 sqlite3_finalize(stmt);
502
503 if (*count == 0) {
504 free(prompts);
505 return NULL;
506 }
507
508 return prompts;
509}
510
511int db_update_user_prompt(int id, const char* prompt, int test_suite_id) {
512 if (!db || id <= 0) {
513 return -1;
514 }
515
516 if (!prompt && test_suite_id <= 0) {
517 return 0;
518 }
519
520 char sql[256] = "UPDATE user_prompts SET ";
521
522 if (prompt && test_suite_id > 0) {
523 strcat(sql, "prompt = ?, test_suite_id = ? WHERE id = ?;");
524 } else if (prompt) {
525 strcat(sql, "prompt = ? WHERE id = ?;");
526 } else {
527 strcat(sql, "test_suite_id = ? WHERE id = ?;");
528 }
529
530 sqlite3_stmt* stmt;
531 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
532 if (rc != SQLITE_OK) {
533 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
534 return -1;
535 }
536
537 int param = 1;
538 if (prompt) sqlite3_bind_text(stmt, param++, prompt, -1, SQLITE_TRANSIENT);
539 if (test_suite_id > 0) sqlite3_bind_int(stmt, param++, test_suite_id);
540 sqlite3_bind_int(stmt, param, id);
541
542 rc = sqlite3_step(stmt);
543 sqlite3_finalize(stmt);
544
545 if (rc != SQLITE_DONE) {
546 fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db));
547 return -1;
548 }
549
550 return 0;
551}
552
554 if (!db || id <= 0) {
555 return -1;
556 }
557
558 const char* sql = "DELETE FROM user_prompts WHERE id = ?;";
559
560 sqlite3_stmt* stmt;
561 int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
562 if (rc != SQLITE_OK) {
563 fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
564 return -1;
565 }
566
567 sqlite3_bind_int(stmt, 1, id);
568
569 rc = sqlite3_step(stmt);
570 sqlite3_finalize(stmt);
571
572 if (rc != SQLITE_DONE) {
573 fprintf(stderr, "Execution failed: %s\n", sqlite3_errmsg(db));
574 return -1;
575 }
576
577 return 0;
578}
579
581 if (prompt) {
582 free(prompt->prompt);
583 free(prompt);
584 }
585}
586
587void db_free_user_prompts(UserPrompt** prompts, int count) {
588 if (prompts) {
589 for (int i = 0; i < count; i++) {
590 db_free_user_prompt(prompts[i]);
591 }
592 free(prompts);
593 }
594}
int db_delete_test_suite(int id)
Delete a test suite (cascading deletes associated user prompts).
Definition db.c:277
UserPrompt ** db_get_user_prompts_by_suite(int test_suite_id, int *count)
Retrieve all user prompts for a specific test suite.
Definition db.c:389
static const char * CREATE_TABLES_SQL
Definition db.c:20
void db_close(void)
Close the database connection and release any resources.
Definition db.c:76
TestSuite * db_get_test_suite(int id)
Retrieve a test suite by ID.
Definition db.c:117
void db_free_test_suite(TestSuite *suite)
Free a TestSuite structure.
Definition db.c:304
int db_create_user_prompt(const char *prompt, int test_suite_id)
Create a new user prompt associated with a test suite.
Definition db.c:325
int db_update_test_suite(int id, const char *title, const char *description, const char *system_prompt, const char *model)
Update an existing test suite.
Definition db.c:218
UserPrompt * db_get_user_prompt(int id)
Retrieve a user prompt by ID.
Definition db.c:353
void db_free_user_prompts(UserPrompt **prompts, int count)
Free an array of UserPrompt structures.
Definition db.c:587
UserPrompt ** db_get_all_user_prompts(int *count)
Retrieve all user prompts.
Definition db.c:451
int db_init(const char *db_path)
Initialize the database.
Definition db.c:35
void db_free_user_prompt(UserPrompt *prompt)
Free a UserPrompt structure.
Definition db.c:580
int db_update_user_prompt(int id, const char *prompt, int test_suite_id)
Update an existing user prompt.
Definition db.c:511
TestSuite ** db_get_all_test_suites(int *count)
Retrieve all test suites.
Definition db.c:156
static sqlite3 * db
Definition db.c:18
int db_delete_user_prompt(int id)
Delete a user prompt.
Definition db.c:553
int db_create_test_suite(const char *title, const char *description, const char *system_prompt, const char *model)
Create a new test suite.
Definition db.c:85
void db_free_test_suites(TestSuite **suites, int count)
Free an array of TestSuite structures.
Definition db.c:314
Represents a test suite in the database.
Definition db.h:34
char * title
Definition db.h:36
char * description
Definition db.h:37
int id
Definition db.h:35
char * model
Definition db.h:39
char * system_prompt
Definition db.h:38
Represents a user prompt associated with a test suite.
Definition db.h:51
char * prompt
Definition db.h:53
int test_suite_id
Definition db.h:54
int id
Definition db.h:52