411 lines
9.5 KiB
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;
|
|
}
|