// dvxSql.c -- DVX SQL database implementation // // Wraps SQLite3 with integer handle management. Databases and // cursors are stored in static tables indexed by handle. Handle 0 // is reserved (invalid). #include "dvxSql.h" #include "thirdparty/sqlite/examples/sqlite3.h" #include #include #include #include // ============================================================ // Handle tables // ============================================================ #define MAX_DBS 16 #define MAX_CURSORS 64 typedef struct { sqlite3 *db; char lastError[256]; int32_t affectedRows; } DbEntryT; typedef struct { sqlite3_stmt *stmt; int32_t dbHandle; // which database owns this cursor bool eof; // true after sqlite3_step returns SQLITE_DONE bool started; // true after first dvxSqlNext call } CursorEntryT; static DbEntryT sDbs[MAX_DBS]; static CursorEntryT sCursors[MAX_CURSORS]; // ============================================================ // Internal helpers // ============================================================ static DbEntryT *getDb(int32_t handle) { if (handle <= 0 || handle > MAX_DBS) { return NULL; } if (!sDbs[handle - 1].db) { return NULL; } return &sDbs[handle - 1]; } static CursorEntryT *getCursor(int32_t handle) { if (handle <= 0 || handle > MAX_CURSORS) { return NULL; } if (!sCursors[handle - 1].stmt) { return NULL; } return &sCursors[handle - 1]; } // ============================================================ // dvxSqlOpen // ============================================================ int32_t dvxSqlOpen(const char *path) { if (!path) { return 0; } // Find a free slot for (int32_t i = 0; i < MAX_DBS; i++) { if (!sDbs[i].db) { int rc = sqlite3_open(path, &sDbs[i].db); if (rc != SQLITE_OK) { if (sDbs[i].db) { snprintf(sDbs[i].lastError, sizeof(sDbs[i].lastError), "%s", sqlite3_errmsg(sDbs[i].db)); sqlite3_close(sDbs[i].db); sDbs[i].db = NULL; } return 0; } sDbs[i].lastError[0] = '\0'; sDbs[i].affectedRows = 0; return i + 1; // 1-based handle } } return 0; // no free slots } // ============================================================ // dvxSqlClose // ============================================================ void dvxSqlClose(int32_t db) { DbEntryT *entry = getDb(db); if (!entry) { return; } // Close any open cursors belonging to this database for (int32_t i = 0; i < MAX_CURSORS; i++) { if (sCursors[i].stmt && sCursors[i].dbHandle == db) { sqlite3_finalize(sCursors[i].stmt); memset(&sCursors[i], 0, sizeof(CursorEntryT)); } } sqlite3_close(entry->db); memset(entry, 0, sizeof(DbEntryT)); } // ============================================================ // dvxSqlExec // ============================================================ bool dvxSqlExec(int32_t db, const char *sql) { DbEntryT *entry = getDb(db); if (!entry || !sql) { return false; } char *errMsg = NULL; int rc = sqlite3_exec(entry->db, sql, NULL, NULL, &errMsg); if (rc != SQLITE_OK) { if (errMsg) { snprintf(entry->lastError, sizeof(entry->lastError), "%s", errMsg); sqlite3_free(errMsg); } else { snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db)); } return false; } entry->lastError[0] = '\0'; entry->affectedRows = sqlite3_changes(entry->db); return true; } // ============================================================ // dvxSqlError // ============================================================ const char *dvxSqlError(int32_t db) { DbEntryT *entry = getDb(db); if (!entry) { return "Invalid database handle"; } return entry->lastError; } // ============================================================ // dvxSqlAffectedRows // ============================================================ int32_t dvxSqlAffectedRows(int32_t db) { DbEntryT *entry = getDb(db); if (!entry) { return 0; } return entry->affectedRows; } // ============================================================ // dvxSqlQuery // ============================================================ int32_t dvxSqlQuery(int32_t db, const char *sql) { DbEntryT *entry = getDb(db); if (!entry || !sql) { return 0; } // Find a free cursor slot for (int32_t i = 0; i < MAX_CURSORS; i++) { if (!sCursors[i].stmt) { sqlite3_stmt *stmt = NULL; int rc = sqlite3_prepare_v2(entry->db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db)); return 0; } entry->lastError[0] = '\0'; sCursors[i].stmt = stmt; sCursors[i].dbHandle = db; sCursors[i].eof = false; sCursors[i].started = false; return i + 1; // 1-based handle } } snprintf(entry->lastError, sizeof(entry->lastError), "Too many open cursors"); return 0; } // ============================================================ // dvxSqlNext // ============================================================ bool dvxSqlNext(int32_t rs) { CursorEntryT *cur = getCursor(rs); if (!cur || cur->eof) { return false; } int rc = sqlite3_step(cur->stmt); cur->started = true; if (rc == SQLITE_ROW) { return true; } cur->eof = true; return false; } // ============================================================ // dvxSqlEof // ============================================================ bool dvxSqlEof(int32_t rs) { CursorEntryT *cur = getCursor(rs); if (!cur) { return true; } return cur->eof; } // ============================================================ // dvxSqlFieldCount // ============================================================ int32_t dvxSqlFieldCount(int32_t rs) { CursorEntryT *cur = getCursor(rs); if (!cur) { return 0; } return sqlite3_column_count(cur->stmt); } // ============================================================ // dvxSqlFieldName // ============================================================ const char *dvxSqlFieldName(int32_t rs, int32_t col) { CursorEntryT *cur = getCursor(rs); if (!cur) { return ""; } const char *name = sqlite3_column_name(cur->stmt, col); return name ? name : ""; } // ============================================================ // dvxSqlFieldText // ============================================================ const char *dvxSqlFieldText(int32_t rs, int32_t col) { CursorEntryT *cur = getCursor(rs); if (!cur) { return ""; } const char *text = (const char *)sqlite3_column_text(cur->stmt, col); return text ? text : ""; } // ============================================================ // dvxSqlFieldByName // ============================================================ const char *dvxSqlFieldByName(int32_t rs, const char *name) { CursorEntryT *cur = getCursor(rs); if (!cur || !name) { return ""; } int32_t colCount = sqlite3_column_count(cur->stmt); for (int32_t i = 0; i < colCount; i++) { const char *colName = sqlite3_column_name(cur->stmt, i); if (colName && strcasecmp(colName, name) == 0) { const char *text = (const char *)sqlite3_column_text(cur->stmt, i); return text ? text : ""; } } return ""; } // ============================================================ // dvxSqlFieldInt // ============================================================ int32_t dvxSqlFieldInt(int32_t rs, int32_t col) { CursorEntryT *cur = getCursor(rs); if (!cur) { return 0; } return sqlite3_column_int(cur->stmt, col); } // ============================================================ // dvxSqlFieldDbl // ============================================================ double dvxSqlFieldDbl(int32_t rs, int32_t col) { CursorEntryT *cur = getCursor(rs); if (!cur) { return 0.0; } return sqlite3_column_double(cur->stmt, col); } // ============================================================ // dvxSqlFreeResult // ============================================================ void dvxSqlFreeResult(int32_t rs) { CursorEntryT *cur = getCursor(rs); if (!cur) { return; } sqlite3_finalize(cur->stmt); memset(cur, 0, sizeof(CursorEntryT)); } // ============================================================ // dvxSqlEscape // ============================================================ int32_t dvxSqlEscape(const char *src, char *dst, int32_t dstSize) { if (!src || !dst || dstSize <= 0) { return -1; } int32_t di = 0; for (int32_t si = 0; src[si]; si++) { if (src[si] == '\'') { if (di + 2 >= dstSize) { return -1; } dst[di++] = '\''; dst[di++] = '\''; } else { if (di + 1 >= dstSize) { return -1; } dst[di++] = src[si]; } } dst[di] = '\0'; return di; }