DVX_GUI/sql/dvxSql.c

411 lines
9.5 KiB
C

// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// ============================================================
// 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;
}