cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
db.c
Go to the documentation of this file.
1#include <cwist/core/db/sql.h>
6#include <sqlite3.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <ctype.h>
11#include <stdbool.h>
12
19typedef struct {
20 uint32_t join_count;
21 uint32_t predicate_count;
22 uint64_t symbolic_weight;
23} cwist_db_plan_hint;
24
25/* Bhaskara II style integer-root refinement keeps everything in integer space. */
31static uint64_t cwist_db_integer_root(uint64_t value) {
32 if (value == 0) return 0;
33 uint64_t x = value;
34 uint64_t y = (x + 1) >> 1;
35 while (y < x) {
36 x = y;
37 y = (x + value / x) >> 1;
38 }
39 return x;
40}
41
47static bool cwist_db_is_alpha(char c) {
48 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
49}
50
58static bool cwist_db_match_keyword(const char *sql, size_t pos, const char *kw) {
59 size_t i = 0;
60 while (kw[i]) {
61 char current = sql[pos + i];
62 if (!current) return false;
63 if ((char)toupper((unsigned char)current) != kw[i]) {
64 return false;
65 }
66 ++i;
67 }
68 if (pos > 0) {
69 char prev = sql[pos - 1];
70 if (cwist_db_is_alpha(prev)) return false;
71 }
72 char next = sql[pos + i];
73 if (cwist_db_is_alpha(next)) return false;
74 return true;
75}
76
82static cwist_db_plan_hint cwist_db_analyze_sql(const char *sql) {
83 cwist_db_plan_hint hint = {0, 0, 0};
84 if (!sql) return hint;
85 size_t len = strlen(sql);
86 uint64_t accum = 0xcbf29ce484222325ULL;
87 uint32_t depth = 0;
88 for (size_t i = 0; i < len; ++i) {
89 char upper = (char)toupper((unsigned char)sql[i]);
90 accum ^= (uint64_t)upper;
91 accum *= 0x100000001b3ULL;
92 if (upper == '(')
93 depth++;
94 else if (upper == ')' && depth > 0)
95 depth--;
96
97 if (upper == 'J' && cwist_db_match_keyword(sql, i, "JOIN")) {
98 hint.join_count++;
99 } else if (upper == 'W' && cwist_db_match_keyword(sql, i, "WHERE")) {
100 hint.predicate_count++;
101 } else if (upper == 'A' && cwist_db_match_keyword(sql, i, "AND")) {
102 hint.predicate_count++;
103 } else if (upper == 'O' && cwist_db_match_keyword(sql, i, "OR")) {
104 hint.predicate_count++;
105 }
106 }
107 hint.symbolic_weight = accum ^ ((uint64_t)depth << 48);
108 return hint;
109}
110
116static void cwist_db_apply_hint(sqlite3 *conn, const cwist_db_plan_hint *hint) {
117 if (!conn || !hint) return;
118 uint64_t complexity = hint->symbolic_weight + ((uint64_t)hint->join_count * 131ULL) +
119 ((uint64_t)hint->predicate_count * 53ULL) + 1ULL;
120 uint64_t root = cwist_db_integer_root(complexity);
121 int busy_ms = 50 + (int)(root % 350);
122 sqlite3_busy_timeout(conn, busy_ms);
123 if (hint->join_count >= 2) {
124 sqlite3_exec(conn, "PRAGMA temp_store = MEMORY;", NULL, NULL, NULL);
125 }
126 if (hint->predicate_count >= 3) {
127 sqlite3_exec(conn, "PRAGMA cache_spill = 0;", NULL, NULL, NULL);
128 }
129}
130
131// Make SQLite Error type as cwist_error_t
138static cwist_error_t make_sqlite_error(int rc, char *msg) {
140 err.error.err_json = cJSON_CreateObject();
141 cJSON_AddNumberToObject(err.error.err_json, "sqlite_rc", rc);
142 cJSON_AddStringToObject(err.error.err_json, "message", msg ? msg : "Unknown Error");
143 return err;
144}
145
146// Open SQLite database file
147// 0 on success, -1 on failure
154cwist_error_t cwist_db_open(cwist_db **db, const char *path) {
156
157 // if there's no db or path, return -1
158 if (!db || !path) {
159 err.error.err_i16 = -1;
160 return err;
161 }
162
163 // if malloc fails, return -1
164 *db = (cwist_db *)cwist_alloc(sizeof(cwist_db));
165 if (!*db) {
166 err.error.err_i16 = -1;
167 return err;
168 }
169
170 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
171 if (strncmp(path, "file:", 5) == 0) flags |= SQLITE_OPEN_URI;
172 int rc = sqlite3_open_v2(path, &(*db)->conn, flags, NULL);
173 if (rc) {
174 cwist_error_t sql_err = make_sqlite_error(rc, (char *)sqlite3_errmsg((*db)->conn));
175 sqlite3_close((*db)->conn);
176 cwist_free(*db);
177 *db = NULL;
178 return sql_err;
179 }
180
181 err.error.err_i16 = 0;
182 return err;
183}
184
198cwist_error_t cwist_db_open_memory(cwist_db **db, const void *buf, size_t len, int readonly) {
200 if (!db || !buf || len == 0) {
201 err.error.err_i16 = -1;
202 return err;
203 }
204
205 *db = (cwist_db *)cwist_alloc(sizeof(cwist_db));
206 if (!*db) {
207 err.error.err_i16 = -1;
208 return err;
209 }
210
211 int rc =
212 sqlite3_open_v2(":memory:", &(*db)->conn,
213 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);
214 if (rc) {
215 cwist_error_t sql_err = make_sqlite_error(rc, (char *)sqlite3_errmsg((*db)->conn));
216 sqlite3_close((*db)->conn);
217 cwist_free(*db);
218 *db = NULL;
219 return sql_err;
220 }
221
222 /* Copy into SQLite-owned memory: FREEONCLOSE releases it on
223 * sqlite3_close() — including the deserialize failure path. */
224 unsigned char *image = (unsigned char *)sqlite3_malloc64((sqlite3_uint64)len);
225 if (!image) {
226 sqlite3_close((*db)->conn);
227 cwist_free(*db);
228 *db = NULL;
229 err.error.err_i16 = -1;
230 return err;
231 }
232 memcpy(image, buf, len);
233
234 unsigned flags = SQLITE_DESERIALIZE_FREEONCLOSE |
235 (readonly ? SQLITE_DESERIALIZE_READONLY : SQLITE_DESERIALIZE_RESIZEABLE);
236 rc = sqlite3_deserialize((*db)->conn, "main", image, (sqlite3_int64)len, (sqlite3_int64)len,
237 flags);
238 if (rc != SQLITE_OK) {
239 cwist_error_t sql_err = make_sqlite_error(rc, (char *)sqlite3_errmsg((*db)->conn));
240 sqlite3_close((*db)->conn);
241 cwist_free(*db);
242 *db = NULL;
243 return sql_err;
244 }
245
246 err.error.err_i16 = 0;
247 return err;
248}
249
257cwist_error_t cwist_db_serialize(cwist_db *db, void **out, size_t *out_len) {
259 if (!db || !db->conn || !out || !out_len) {
260 err.error.err_i16 = -1;
261 return err;
262 }
263 *out = NULL;
264 *out_len = 0;
265
266 sqlite3_int64 size = 0;
267 unsigned char *image = sqlite3_serialize(db->conn, "main", &size, 0);
268 if (!image || size <= 0) {
269 if (image) sqlite3_free(image);
270 err.error.err_i16 = -1;
271 return err;
272 }
273
274 /* Copy into cwist-managed memory so callers free uniformly with
275 * cwist_free() instead of mixing in sqlite3_free(). */
276 void *copy = cwist_alloc((size_t)size);
277 if (!copy) {
278 sqlite3_free(image);
279 err.error.err_i16 = -1;
280 return err;
281 }
282 memcpy(copy, image, (size_t)size);
283 sqlite3_free(image);
284
285 *out = copy;
286 *out_len = (size_t)size;
287 err.error.err_i16 = 0;
288 return err;
289}
290
296 if (db) {
297 if (db->conn) {
298 sqlite3_close(db->conn);
299 }
300 cwist_free(db);
301 }
302}
303
304// Execute given SQL command
305// return errmsg on failure
312cwist_error_t cwist_db_exec(cwist_db *db, const char *sql) {
314 if (!db || !db->conn || !sql) {
315 err.error.err_i16 = -1;
316 return err;
317 }
318
319 char *zErrMsg = 0;
320 cwist_db_plan_hint hint = cwist_db_analyze_sql(sql);
321 cwist_db_apply_hint(db->conn, &hint);
322 int rc = sqlite3_exec(db->conn, sql, 0, 0, &zErrMsg);
323
324 if (rc != SQLITE_OK) {
325 cwist_error_t err = make_sqlite_error(rc, zErrMsg);
326 sqlite3_free(zErrMsg);
327 return err;
328 }
329
330 err.error.err_i16 = 0;
331 return err;
332}
333
334// Callback for converting rows to JSON
335typedef struct {
336 cJSON *rows;
337} query_context;
338
347static int query_callback(void *data, int argc, char **argv, char **azColName) {
348 query_context *ctx = (query_context *)data;
349 cJSON *row = cJSON_CreateObject();
350
351 for (int i = 0; i < argc; i++) {
352 // SQLite returns everything as char* by default in exec callback
353 // For simplicity, we treat everything as string or try to guess types if needed.
354 // But for a generic wrapper, string is safest unless we parse schema.
355 if (argv[i]) {
356 cJSON_AddStringToObject(row, azColName[i], argv[i]);
357 } else {
358 cJSON_AddNullToObject(row, azColName[i]);
359 }
360 }
361
362 cJSON_AddItemToArray(ctx->rows, row);
363 return 0;
364}
365
366// execute a query and store result at result pointer
374cwist_error_t cwist_db_query(cwist_db *db, const char *sql, cJSON **result) {
375 if (!result) {
377 err.error.err_i16 = -1;
378 return err;
379 }
380 *result = NULL;
381 if (!db || !db->conn || !sql) {
383 err.error.err_i16 = -1;
384 return err;
385 }
386
387 query_context ctx;
388 ctx.rows = cJSON_CreateArray();
389
390 char *zErrMsg = 0;
391 cwist_db_plan_hint hint = cwist_db_analyze_sql(sql);
392 cwist_db_apply_hint(db->conn, &hint);
393 int rc = sqlite3_exec(db->conn, sql, query_callback, &ctx, &zErrMsg);
394
395 if (rc != SQLITE_OK) {
396 cJSON_Delete(ctx.rows);
397 cwist_error_t err = make_sqlite_error(rc, zErrMsg);
398 sqlite3_free(zErrMsg);
399 return err;
400 }
401
402 *result = ctx.rows;
403
405 err.error.err_i16 = 0;
406 return err;
407}
408
409/* ==========================================================================
410 * cwist_db_insert_healed
411 * ======================================================================== */
412
413/*
414 * Build an INSERT statement from a cJSON object.
415 * Returns a heap-allocated (sqlite3_malloc) SQL string, or NULL on failure.
416 * The caller must free it with sqlite3_free().
417 *
418 * String values are escaped with %Q; numbers, booleans and nulls are
419 * rendered directly. Nested objects/arrays are serialised to a JSON
420 * string so they can be stored as TEXT.
421 */
428static char *build_insert_sql(const char *table, const cJSON *obj) {
429 /* Collect column/value fragments. */
430 int count = cJSON_GetArraySize((cJSON *)obj);
431 if (count <= 0) return NULL;
432
433 /* Worst-case sizes – we'll sqlite3_mprintf each fragment individually. */
434 char *cols_buf = (char *)sqlite3_malloc(count * 64 + 16);
435 char *vals_buf = (char *)sqlite3_malloc(count * 256 + 16);
436 if (!cols_buf || !vals_buf) {
437 sqlite3_free(cols_buf);
438 sqlite3_free(vals_buf);
439 return NULL;
440 }
441 cols_buf[0] = '\0';
442 vals_buf[0] = '\0';
443
444 const cJSON *child = obj->child;
445 bool first = true;
446 while (child) {
447 /* Column name – double-quoted for safety */
448 char col_frag[128];
449 snprintf(col_frag, sizeof(col_frag), first ? "\"%s\"" : ",\"%s\"", child->string);
450 size_t cols_cap = (size_t)(count * 64 + 16);
451 size_t cols_used = strlen(cols_buf);
452 if (cols_used < cols_cap - 1) strncat(cols_buf, col_frag, cols_cap - 1 - cols_used);
453
454 /* Value */
455 char *val_frag = NULL;
456 if (cJSON_IsString(child)) {
457 val_frag = sqlite3_mprintf(first ? "%Q" : ",%Q", child->valuestring);
458 } else if (cJSON_IsNumber(child)) {
459 val_frag = sqlite3_mprintf(first ? "%g" : ",%g", child->valuedouble);
460 } else if (cJSON_IsTrue(child)) {
461 val_frag = sqlite3_mprintf(first ? "1" : ",1");
462 } else if (cJSON_IsFalse(child)) {
463 val_frag = sqlite3_mprintf(first ? "0" : ",0");
464 } else if (cJSON_IsNull(child)) {
465 val_frag = sqlite3_mprintf(first ? "NULL" : ",NULL");
466 } else {
467 /* Object / Array → store as JSON text */
468 char *serialised = cJSON_PrintUnformatted(child);
469 if (serialised) {
470 val_frag = sqlite3_mprintf(first ? "%Q" : ",%Q", serialised);
471 free(serialised);
472 }
473 }
474
475 if (!val_frag) {
476 sqlite3_free(cols_buf);
477 sqlite3_free(vals_buf);
478 return NULL;
479 }
480 size_t vals_cap = (size_t)(count * 256 + 16);
481 size_t vals_used = strlen(vals_buf);
482 if (vals_used < vals_cap - 1) strncat(vals_buf, val_frag, vals_cap - 1 - vals_used);
483 sqlite3_free(val_frag);
484
485 first = false;
486 child = child->next;
487 }
488
489 char *sql = sqlite3_mprintf("INSERT INTO \"%s\" (%s) VALUES (%s);", table, cols_buf, vals_buf);
490 sqlite3_free(cols_buf);
491 sqlite3_free(vals_buf);
492 return sql;
493}
494
504cwist_error_t cwist_db_insert_healed(cwist_db *db, const char *table, const char *json_str,
505 const cwist_schema_t *schema,
506 const cwist_heal_config_t *heal_cfg) {
508 err.error.err_i16 = -1;
509
510 if (!db || !table || !json_str) return err;
511
512 /* Build effective heal config (merge schema if not already set). */
513 cwist_heal_config_t effective_cfg;
514 if (heal_cfg) {
515 effective_cfg = *heal_cfg;
516 } else {
517 memset(&effective_cfg, 0, sizeof(effective_cfg));
518 effective_cfg.threshold = 0.8;
519 }
520 if (schema && !effective_cfg.schema) effective_cfg.schema = schema;
521
522 /* Step 1: heal the JSON. */
523 cwist_heal_result_t healed = cwist_json_heal(json_str, &effective_cfg);
524 if (!healed.json) {
525 fprintf(stderr, "[CWIST-DB] insert_healed: healing failed for table '%s'\n", table);
526 cwist_heal_result_free(&healed);
527 return err;
528 }
529
530 /* Step 2: strict Zod validation if a schema is provided. */
531 if (schema) {
532 cJSON *validated = NULL;
533 cwist_zod_result_t zod = cwist_zod_parse(healed.json, schema, &validated);
534 if (!zod.valid) {
535 fprintf(stderr, "[CWIST-DB] insert_healed: Zod validation failed for table '%s':\n",
536 table);
538 cwist_heal_result_free(&healed);
539 return err;
540 }
541 if (validated) cJSON_Delete(validated);
542 }
543
544 /* Step 3: parse the healed JSON and build INSERT SQL. */
545 cJSON *obj = cJSON_Parse(healed.json);
546 cwist_heal_result_free(&healed);
547 if (!obj || !cJSON_IsObject(obj)) {
548 if (obj) cJSON_Delete(obj);
549 return err;
550 }
551
552 char *sql = build_insert_sql(table, obj);
553 cJSON_Delete(obj);
554 if (!sql) return err;
555
556 /* Step 4: execute. */
557 err = cwist_db_exec(db, sql);
558 sqlite3_free(sql);
559 return err;
560}
561
562/* ==========================================================================
563 * cwist_db_query_strict
564 * ======================================================================== */
565
574cwist_error_t cwist_db_query_strict(cwist_db *db, const char *sql, cJSON **result,
575 const cwist_schema_t *schema) {
576 /* When no schema is given, behave identically to cwist_db_query(). */
577 if (!schema) return cwist_db_query(db, sql, result);
578
579 cJSON *all = NULL;
580 cwist_error_t err = cwist_db_query(db, sql, &all);
581 if (err.errtype == CWIST_ERR_JSON || !all) return err;
582
583 cJSON *conforming = cJSON_CreateArray();
584 if (!conforming) {
585 cJSON_Delete(all);
586 err.error.err_i16 = -1;
587 return err;
588 }
589
590 cJSON *row = NULL;
591 cJSON_ArrayForEach(row, all) {
592 cwist_zod_result_t zod = cwist_zod_validate(row, schema);
593 if (zod.valid) {
594 cJSON *clone = cJSON_Duplicate(row, 1);
595 if (clone) cJSON_AddItemToArray(conforming, clone);
596 }
597 }
598
599 cJSON_Delete(all);
600 *result = conforming;
601
603 err.error.err_i16 = 0;
604 return err;
605}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
Database operations wrapper (SQLite3).
cwist_error_t cwist_db_query(cwist_db *db, const char *sql, cJSON **result)
Execute a SQL query and collect every row into a cJSON array.
Definition db.c:374
cwist_error_t cwist_db_open(cwist_db **db, const char *path)
Open a SQLite database and wrap the resulting connection in a CWIST handle.
Definition db.c:154
cwist_error_t cwist_db_exec(cwist_db *db, const char *sql)
Execute an arbitrary SQL statement string without returning rows.
Definition db.c:312
cwist_error_t cwist_db_serialize(cwist_db *db, void **out, size_t *out_len)
Serialize a CWIST database into a freshly allocated image buffer.
Definition db.c:257
cwist_error_t cwist_db_query_strict(cwist_db *db, const char *sql, cJSON **result, const cwist_schema_t *schema)
Execute a query and require the result rows to satisfy a CWIST schema.
Definition db.c:574
cwist_error_t cwist_db_open_memory(cwist_db **db, const void *buf, size_t len, int readonly)
Open a CWIST database from an in-memory SQLite image.
Definition db.c:198
void cwist_db_close(cwist_db *db)
Close a CWIST database wrapper and its underlying SQLite connection.
Definition db.c:295
cwist_error_t cwist_db_insert_healed(cwist_db *db, const char *table, const char *json_str, const cwist_schema_t *schema, const cwist_heal_config_t *heal_cfg)
Heal, validate, and insert a JSON object into a table.
Definition db.c:504
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
Definition cwist_err.h:16
@ CWIST_ERR_JSON
Definition cwist_err.h:37
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
Definition error.c:14
Self-Healing JSON Recovery Layer (CWIST)
void cwist_heal_result_free(cwist_heal_result_t *r)
Free resources owned by a cwist_heal_result_t.
Definition json_heal.c:584
cwist_heal_result_t cwist_json_heal(const char *input, const cwist_heal_config_t *cfg)
Attempt to heal a potentially broken JSON string.
Definition json_heal.c:509
size_t count
Definition otel.c:58
Wrapper for Database Operations. Currently uses SQLite3.
Definition sql.h:21
sqlite3 * conn
Definition sql.h:22
Integrated error structure used throughout the system.
Definition cwist_err.h:72
cwist_errtype_t errtype
Definition cwist_err.h:73
__prim_cwist_error_t error
Definition cwist_err.h:74
Configuration passed to cwist_json_heal().
Definition json_heal.h:97
const cwist_schema_t * schema
Schema for L2 alignment (may be NULL).
Definition json_heal.h:99
double threshold
Min confidence (0.0–1.0) to accept recovery. Default: 0.8.
Definition json_heal.h:98
Result of a healing attempt.
Definition json_heal.h:111
char * json
Healed JSON string (free with cwist_heal_result_free). NULL on failure.
Definition json_heal.h:112
A complete schema: array of field descriptors + count.
Definition json_heal.h:66
Result returned by cwist_zod_validate() / cwist_zod_parse().
Definition zod.h:47
bool valid
Definition zod.h:48
Unified error code and type definitions for the CWIST system layer.
Strict JSON schema validation (Zod-style) for CWIST.
cwist_zod_result_t cwist_zod_validate(const cJSON *json, const cwist_schema_t *schema)
Strictly validate an already-parsed cJSON object against a schema.
Definition zod.c:65
void cwist_zod_print_errors(const cwist_zod_result_t *r)
Print all validation errors to stderr.
Definition zod.c:146
cwist_zod_result_t cwist_zod_parse(const char *raw, const cwist_schema_t *schema, cJSON **out)
Parse a raw JSON string and validate it in one step.
Definition zod.c:112